`
chelsea
  • 浏览: 117735 次
  • 来自: ...
社区版块
存档分类
最新评论

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, 以及概念的缺失带来的理解上的困难. 但这不意味着要不加辨别的封装所有的数据和行为, 把涉及到某片数据的行为都封装在一起. 事实上我们缺乏仔细的分析而做了过多的封装, 是时候把数据和不合适的行为拆开了, 拆的原则就是稳定性和使用场景

推论二, 类的方法只应该操作自己的数据, 方法参数只应该是基础类型或自己的成员类型.

当你发现两个类的对象有交互从而把交互放在任何一方都会违反上述原则的时候, 定义一个交互类,从而三个类又都满足上面的原则

 

分享到:
评论

相关推荐

    dci-sample:DCI(数据-上下文-交互)代码示例

    2. **可扩展性**:新业务场景可以通过添加新的角色或上下文轻松实现。 3. **测试友好**:角色可以独立于数据对象进行单元测试,简化了测试编写。 然而,DCI模式的普及度相对较低,且在某些场景下,其复杂性可能高于...

    DDD领域驱动设计 DCI架构

    DDD的核心理念是通过领域建模来理解和表述业务逻辑,以提高软件系统的可读性、可维护性和可扩展性。领域模型是DDD的关键组成部分,它不仅仅是一个静态的设计文档,而是活生生的、随着业务发展而不断演化的模型。 在...

    Python库 | dci_utils-0.0.402.tar.gz

    这些库大大简化了开发过程,节省了开发者的时间,提高了代码的可复用性和效率。dci_utils库很可能包含了一些实用工具,旨在帮助开发者处理日常的编程任务。 dci_utils-0.0.402.tar.gz文件是一个常见的压缩格式,...

    PyPI 官网下载 | dci_utils-0.0.309.tar.gz

    在云原生环境中,dci_utils 可能帮助开发者更好地管理和部署分布式应用,提高系统的可伸缩性和可靠性。 **Python库的使用** 在Python项目中引入dci_utils库,首先需要通过pip进行安装,假设已经下载了dci_utils-...

    dciwebsolutions:DCI Web解决方案网站

    DCI架构强调数据模型、内容呈现和用户交互的分离,旨在提高代码可读性、可维护性和复用性。 【标签】虽然没有提供具体的标签,我们可以推测这个项目可能涉及到以下几个关键知识点: 1. **DCI架构**:这是一种面向...

    Python库 | dci_utils-0.0.661-py2-none-any.whl

    6. 安全性:考虑到数据采集可能涉及隐私和安全问题,库可能提供了安全的HTTP请求选项,如HTTPS支持,以及数据加密和解密的工具。 7. 自定义配置:库可能允许用户通过配置文件或参数设置自定义其行为,以适应不同的...

    PyPI 官网下载 | dci_utils-0.0.79.tar.gz

    首先,`dci_utils`是一个Python库,这意味着它提供了一系列的函数和类,旨在简化开发过程,提高代码复用性。Python库通常由一系列模块组成,每个模块负责特定的功能,它们可以被其他Python程序导入和使用。`dci_...

    PyPI 官网下载 | dci_utils-0.0.1074.tar.gz

    虽然具体的功能需要通过查看源代码或官方文档才能明确,但我们可以推测dci_utils是为了解决云原生环境下的实际问题而设计的,它将Python的简洁性和易用性与分布式系统的需求相结合,为开发者提供了一套实用的工具集...

    Python库 | dci_utils-0.0.808.tar.gz

    `dci_utils`很可能也包含测试代码,以确保其功能的正确性和稳定性。开发者在使用过程中如果遇到问题,可以通过查看库的源代码、阅读文档或提交问题报告来寻求帮助。 总的来说,`dci_utils`作为一款Python库,虽然...

    Python库 | dci_utils-0.0.698-py2-none-any.whl

    使用dci_utils不仅可以减少重复劳动,还可以确保代码质量,因为它已经过测试和优化。为了充分利用这个库,开发者需要阅读其文档,理解每个函数或类的作用,以及如何正确地调用它们。 总的来说,“dci_utils-0.0.698...

    PyPI 官网下载 | dci_utils-0.0.1120.tar.gz

    为了更深入地理解和使用这个库,开发者需要解压dci_utils-0.0.1120.tar.gz文件,查看其内部结构,包括源代码、文档和示例,以便学习如何集成到自己的项目中。同时,查阅官方文档或者社区论坛,可以获取更多关于如何...

    PyPI 官网下载 | dci_utils-0.0.593-py2-none-any.whl

    为了确保兼容性,开发者应该检查他们的Python环境是否为Python 2,并且确认`dci_utils`库是否已经更新到了更现代的Python版本,或者是否有提供Python 3的版本。 在实际开发中,理解库的功能和使用方法至关重要。...

    PyPI 官网下载 | dci_utils-0.0.839-py2-none-any.whl

    《PyPI与Python库:dci_utils-0.0.839-...对于`dci_utils`这样的库,理解它的功能和使用方法,可以有效提升开发效率,为项目带来便利。在实际应用中,我们应充分利用官方文档和社区资源,以便更好地掌握和利用这些库。

    Python库 | dci_utils-0.0.815-py2-none-any.whl

    在Python社区,这样的库往往由开发者们为了特定项目需求而创建,旨在提高代码复用性和开发效率。 `.whl`文件是Python的一种二进制分发格式,它是Python Wheel项目的产物,目的是简化Python包的安装过程。与常见的`....

    LTE下行控制信道matlab代码

    本压缩包提供的是一套详细的LTE下行控制信道PDCCH的MATLAB实现代码,通过这个代码,我们可以深入理解PDCCH的构造、编码、调制以及解码过程。MATLAB是一种强大的数学计算和仿真工具,非常适合进行通信系统的建模和...

    pdfbox-app-1.8.5.zip

    DCI旨在提高代码的可读性和可维护性,尤其在复杂的业务逻辑中。 在Scala DCI的实现中,可能会有以下内容: 1. 源码示例:展示如何在Scala中定义和使用DCI角色。 2. 文档:解释DCI的概念,以及在Scala中如何实现和...

Global site tag (gtag.js) - Google Analytics