==DCI: 代码的可理解性

DCI: 代码的可理解性

可理解性: 为什么几十万字的小说看一遍我们就可以理解, 而几千行code却要一读再读?
--Objects are principally about people and their mental models, not polymorphism, coupling and cohesion

代码难以理解是软件行业的痼疾. 众多方法和方法论致力于解决这个问题, 不管主观还是客观. 造成理解困难的原因有很多, 我们今天讨论其中一种: 业务流程被分解在代码中, 支离破碎.
而这个原因的引申问题就是: 业务流程在代码中如何组织? 对于这个问题, 争论从未停止:

Transaction Script vs. Domain Model
贫血模型与充血模型之争
Service存废的争论

造成争论的原因是本质的: OO长于刻画structure, 拙于捕捉behavior. OO在把世界分为多个对象的时候, 把行为也分散了, 我要理解一次交互, 需要在不同的对象的不同方法中跳来跳去. 空间不连续. 有时还用回调,异步等, 时间也不连续了.

尝试

让我们试着跳出软件的范围, 尝试在更广泛的范围内寻找思路, 比如为什么小说和电影有复杂的人物关系和情节, 我们却能轻易理解? 是否跟人理解事物时的Mental Model有关? 如果我们能找出人类理解事物的Mental Model, 据此来编写符合Mental Model的代码是否会提高可理解性? 沿着这个思路走下去, 我们就得到了一种尝试性解决方案: 把世界分解为Data, Context 和 Interaction, 简称DCI
让我们试着从头推导一下.
第一个问题: 当我们错过了开头, 从中间开始看一部电影的时候, 画面上有一个人正在做一件事, 我们会如何入手, 会问什么问题呢?
他是谁?
他是做什么的?
他正在做什么?

这就是我们理解电影剧本或小说的Mental Model: 人物, 角色身份, 然后就是一幕接一幕的场景. 举个例子来说, 电影<<盗梦空间Inception>>中的盗梦团队如下:
The Extractor(盗梦人)
The Architect(筑梦师)
The Point Man(侦察兵)
The Forger(伪造者)
The Tourist(旅客)
The Chemist(药剂师)

盗梦最关键的一步是要在合适的时机穿越回上一层或现实, 电影中叫Kick. 那么 Kick() 这个操作放在哪? 每个人都可以Kick. 这时我们就会想起一个叫做梦主(Dreamer)的角色(Role), Kick应该是Dreamer的操作, 而任何一个人在特定的场景下都可以扮演Dreamer.
class Dreamer {
void Kick();
}

Data
再来看一个稍微贴近软件开发的例子: 转账.
假设储蓄账户的领域模型是一个叫做SavingAccount的class, 它封装了账户余额等属性. 对于如何用它来支持转账操作, 比如取款和存款, 我们至少有两种选择: 我们是仅仅用它来封装简单的余额加减操作, 还是把整个转账流程封装在里面? 也即下面的代码中, Decrease 和 Withdraw 要二选一.
class SavingAccount
{
private Amount balance;
void Decrease(Amount amount) {...}
void Withdraw(Amount amount) {...}
}
从涉及的业务范围, 需要的知识和依赖来看:
Decrease这个操作, 只涉及到Amount, 所需知识无非是数学上的加减运算
而Withdraw, 远远不只是把余额减去多少, 还涉及到事务语义, 用户交互, 恢复, 错误处理, 系统日志以及业务规则, 比如支取额度等. SavingAccount这个类没有能力完成所有的操作

储蓄账户是一个相对稳定的业务概念, 那么简单的Decrease和复杂的Withdraw哪个更能匹配SavingAccount的稳定性呢?
Decrease是非常稳定的, 它涉及的领域概念无非就是数学上的加减运算
而Withdraw发生变化的可能性就大的多, 无论是基础设施的错误处理发生变化, 还是支取额度等规则的变化, 都会导致取款发生变化.

数据模型是相对稳定的, 因此在这里, 我们选择用SavingAccount来表达数据模型, 里面只有Decease等简单的操作数据的方法.
class SavingAccount
{
private Amount balance;
void Decrease(Amount amount) { balance -= amount; }
void Increase(Amount amount) { balance += amount; }
}

Role + Interaction
那么问题来了, 真正的转账操作 Transfer() 放在哪? DCI对此的答案是显式建模, Interaction
交互就自然涉及到Role, 事实上角色是由具体的交互定义的. 如果你不去教课,那么Teacher这个title没有任何意义. 如果你不去跟客户交流, 那么BA这个Role也没任何意义. 换句话说, 只要你在做业务分析,需求分析,此时此刻你就是BA.
那么Transfer涉及到什么角色? Source Account and Destination Account.
Transfer(SourceAccount source, DestinationAccount destination, Amount amount)
{
source.Decrease(amount);
destination.Increase(amount);
...
}

Context
最后一个问题: 谁来指定谁扮演什么角色? DCI的答案是Context
class TransferContext {
void Transfer(SavingAccount source, SavingAccount destination, Amount amount)
{
var sourceAccount = Cast<SourceAccount>(source);
var destinationAccount = Cast<DestinationAccount>(destination);
Transfer(sourceAccount, destinationAccount, amount); // new TransferInteraction(xxx).Transfer();
}
}

DCI

Data: What the system is. (static, structure)
Role + Interaction: What the system does. (dynamic, behavior)
Context: Mapping the data to role, trigger the interaction. (the director)

推论

推论一, 拆! 把行为拆出去.
什么? OO难道不是要封装数据和行为吗? 让我们考一下古. 最初OO说要封装数据和行为. 所解决的问题是对数据访问无法全面控制而导致的隐藏的Bug, 以及概念的缺失带来的理解上的困难. 但这不意味着要不加辨别的封装所有的数据和行为, 把涉及到某片数据的行为都封装在一起. 事实上我们缺乏仔细的分析而做了过多的封装, 是时候把数据和不合适的行为拆开了, 拆的原则就是稳定性和使用场景
推论二, 类的方法只应该操作自己的数据, 方法参数只应该是基础类型或自己的成员类型.
当你发现两个类的对象有交互从而把交互放在任何一方都会违反上述原则的时候, 定义一个交互类,从而三个类又都满足上面的原则

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,369评论 0 23
  • 作者:李桓 今赛季的广州恒大淘宝,真是多事之秋。先是花4200万欧购买的J马火力不足,再是没有报阿兰的名字去踢亚冠...
    帝豪万丈阅读 194评论 0 0
  • 第一次进入H教授的实验室我还是有些胆颤的,虽然在这奇怪的大学里有着形形色色的怪人,但他们除了为我们这些小小本...
    希腊的星空阅读 624评论 0 4
  • 20170921高丽新心赏第38天 亲爱的老公,今天早上一上班,就给我发了关于语文改版新增加的古诗收集。我们还讨论...
    rygao阅读 228评论 0 1