Smoke 與契約式给涕、防御性編程
Smoke 是我寫的一個給Dto做Validation 的小工具(https://github.com/eltonZhong/smoke)。 起因很簡單,之前我在寫Http client的時候, 覺得在很多地方都要對同一個對象進(jìn)行驗(yàn)證比較麻煩(防御性編程)迅腔。 有個注解形式聲明, 在最初的地方對對象進(jìn)行一次驗(yàn)證, 就可以在編碼時假設(shè)數(shù)據(jù)是OK的了。注解形式的validation是聲明式的, 減少了程序員對數(shù)據(jù)是否符合規(guī)范的恐懼靠娱。
想想, 如果這個東西是命令式的, 即你在第一次拿到這個對象的時候, 對它進(jìn)行了基本的驗(yàn)證, 但是到后面的dal 層, service 層, 往往你還會對這個數(shù)據(jù)進(jìn)行再一次的重復(fù)的驗(yàn)證, 才去執(zhí)行自己的業(yè)務(wù)邏輯沧烈。
防御性編程給我們帶來的好處是系統(tǒng)的錯誤盡早發(fā)現(xiàn)處理, 而不是在很后面拋出一個NPE, 到時候造成的損失可能是難以估量的。 而這又帶來一個重復(fù)代碼的問題, 代碼到處驗(yàn)證, 十分臃腫像云。
所以很多人采用約定式的編程, 即我假設(shè)你傳過來的數(shù)據(jù)是符合我的規(guī)范的, 出事怪你锌雀。 我個人覺得這個簡直太可怕了, 就算自己寫的代碼, 我自己也難以信任, 更何況是別人的? 所以, 有個這么一個annotation聲明在類上面, 就像是一個契約文檔迅诬。 我去調(diào)用類的方法, 可以大概知道哪些field是安全的腋逆。 這個是對契約性編程的一種補(bǔ)充。
當(dāng)然, 這遠(yuǎn)遠(yuǎn)不能達(dá)到 保證數(shù)據(jù)一定是安全的
需求侈贷。 那我們?nèi)绾巫瞿兀?/p>
- 保證Dto(或者bean)是immutable 的
- 在每一次對類修改, 都進(jìn)行驗(yàn)證
以上兩個條件滿足其一即可惩歉。
當(dāng)然上面都是我個人的一些看法, 看了一些防御性編程與進(jìn)攻性編程, 覺得他們有部分都沒有講清楚(或者是小弟愚昧看不明白),所以我強(qiáng)行理解了一波, 給出的感觸可能是這樣的。
寫Smoke的感觸
Smoke 不是一個玩具俏蛮。 它是我利用業(yè)余時間寫的, 并在我日常工作上已經(jīng)有應(yīng)用的小工具(或者說框架)撑蚌。
當(dāng)初因?yàn)関alidation重復(fù)代碼太多的問題, 上網(wǎng)找了一些解決方案, 比如hibernate的validator, 還有命令式的, 最終決定針對自己需求寫一個。
剛開始信心滿滿,發(fā)布到j(luò)center, 自己想了好幾個feature, 雖然用不到也實(shí)現(xiàn)了, 然后后面發(fā)現(xiàn)實(shí)在無人問津,就沒再管了搏屑。 留了個VRule沒有實(shí)現(xiàn), 差不多一個月過去了, 今天還是決定把它弄完, 雖然沒有星星, 做事還是要有始有終争涌。 (誰叫我feature沒做好先把readme寫好了。睬棚。承諾過的feature, 浪費(fèi)周末也要搞完第煮。
)
當(dāng)然除了在我自己的工作中用到之外也不是全無收獲, 搞了個travis自動test、publish到j(luò)center, 還踩了一對java庫發(fā)布的坑(fucking java)抑党。之前欠了好多技術(shù)債,一個一個慢慢還包警。
下面是Smoke的文檔~
Smoke [圖片上傳失敗...(image-c07683-1546516134945)]
A declarative validator framework for dtos, beans, or just objects.
Install [圖片上傳失敗...(image-6dbbef-1546516134945)]
Get the latest version at Jcenter
// For gradle
repositories {
jcenter()
}
dependencies {
compile 'com.ez.tools:smoke:5.0.0'
}
Basic Usage
class UserDto {
@VString(shouldBe = {"John1", "John2"})
@VNotNull
String name = null;
}
// Will throw java.lang.IllegalStateException when field name is null
com.ez.tools.validator.Smoke.validate(new UserDto())
Go to package com.ez.tools.validator.annotations
to find other features.
Advance features
Smoke 5.0 focused on the following topics:
- Field validation, with built-in constraints: @VString, @VInt, @VNotNull.
-
Getter method
validation, contraints can be put on method with no arguments, when validating, the getter method will be called, and the value returned will be validated. - @VRule on class level, validate this kind of object using additional rule.
VString
The regex feature has been released! Use regex:
class UserDto {
@VString(shouldMatch = {Regexps.EMAIL})
String email;
// Custom regex: Name should match all values in should match
// And name should not match any values in should not match
@VString(shouldMatch = {".+", "..."})
@VString(shouldNotMatch = {"..."})
String name;
@VString(...)
public void get***() {
}
}
VRule
Use additional rules to validate your dto.
Add additional rule:
com.ez.tools.validator.Smoke.validate(new UserDto(), IRule...rules)
or:
/**
* Add your rule class
**/
@VRule(com.ez.tools.validator.core.rules.AllFieldsShouldNotBeNull.class)
class UserDto {}
// Will validate using the rule specified in VRule value.
com.ez.tools.validator.Smoke.validate(new UserDto())
Also you can write Additional rules:
class YourRule implements IRule {
@Override
public void validate(Object o) {
if (!o instanceof UserDto) {
throw new IllegalArgumentException();
}
UserDto dto = (UserDto) o;
if(dto.name == "elton" && dto.age > 100) {
throw new Exception("Elton is dead after at 99 years old.")
}
}
}
// Use it in your common interface:
@VRule(YourRule.class)
interface SatisfyYourRule {}
// Implements the interface:
class YourDto implements SatisfyYourRule {}
// When validate your dto, the rule will be invoked. And as rule above, Exception("Elton is dead after at 99 years old.") will be thrown
Smoke.validate(new UserDto("elton", 100))
VRecursive
Wanna validate details of a field with smoke? Try VRecursive:
class UserDto {
@VRecursive
AddressDto address = new AddressDto(this);
}
@VRule(AllFieldsShouldNotBeNull.class)
class AddressDto {
public AddressDto(UserDto dto) {
user = dto;
}
String a = null;
@VString(shouldMatch = {Regexps.EMAIL})
String getEmail() {
return "123@qq.com";
}
@VRecursive
UserDto user = null;
}
// Will validate userDto's address, with respect to rules and other annotation con
// And don't worry that it might cause stackoverflow exception:
// smoke will not validate same object twice.
com.ez.tools.validator.Smoke.validate(new UserDto())
Remeber! When field is null, smoke will ignore this field's validation.
You can use VNotNull to handle this situation
class UserDto {
@VString(shouldMatch = "^elton.+")
String name = null;
}
// Will not validate when name is null. If you want to, add @VNotNull annotation constraints.
com.ez.tools.validator.Smoke.validate(new UserDto())