细节和架构同等重要

你能在网上找到「软件架构」的定义,比我要在本文列出的还要多。但是,我希望你能认同我的观点,「软件架构」是系统的较高层次结构,而「软件设计」关乎细节,属于较低层次。

我对此思考越多,就越发意识到:如果你不处理好细节,你也就无法拥有优秀的较高层次结构。如果你没有良好的软件设计,你的软件架构也不会优秀。

有哪些细节呢?

我要讨论微小的设计方面的决定,这也是我们每天、甚至每时每刻都在应对的。

我该怎样命名某个变量、函数或类?我应该拆开某个函数或类吗?我能够保持某个函数是纯函数、或它让这里涉及到的某些状态更加清晰了吗?其它模块通过什么方式访问或使用该模块?我这里漏掉抽象了?开发人员不得不明白这块代码涉及到的概念,那么其它地方还有同样的概念吗?为了便于测试,我该怎样设计该模块的接口;接下来的测试要做什么?这次测试满足轻量、专注、独立和清晰的标准吗?

在写代码时,我们一直都在设计。当你编程时,你不可能没有设计。你不能不回答上面的问题。但是,如果不深入思考,你的答案就算不上最佳的解决方案——或许连良好都算不上。

谨慎

对于保持设计的整洁和良好,如果有团队成员不给予足够重视,那么他们也不会足够重视架构所要保持的良好状态。

代码不会真地「腐烂」。只要我们不碰它,它就一直是原来的样子。我们一旦改动了某处,它好像就变质了。那是因为每个粗心的设计决定造成的,你将因此带来破坏。有时候,危害不大;有时候,却是灾难性的。

保持代码整洁,往往需要大量努力和小心。但是,你不得不这样做,否则,随着时间的流逝,你的进度会变得越来越慢。团队每个成员都要参与,每个人都要谨慎。

良好的命名、和其它设计原则

如果你不能为某些「小东东」找到合适的名字,那么当你不得不修改代码时,你就无从下手。架构文档写得再好,也无济于事。

如果你不坚持依赖反转原则注1,你的技术细节将和业务代码交织在一起。整洁、独立的分层架构也保护不了你。

如果你的类、模块和方法具备多重功能和直接依赖,修改就会波及整个系统。你在架构图里看不到这一点,即使它有着明确的组件。

还有其它设计原则,主要影响着较低层次的设计,并且仍然能够破坏你的架构。我本来想谈谈,但是,你可能会说,「如果它能破坏我的架构,那么它一定属于架构方面的决定」。但是,我认为,这是一种滑坡谬误注2。因为,按照你的说法,每一种设计决定都将成为架构决定

测试和文档

良好的设计,关系到良好的测试。它是介于测试和生产代码之间的、经过良好设计的接口。

测试,尤其是「微测试(micro test)」,可以被软件设计作为优秀的技术文档。但前提是,你编写了良好、独立的测试,且有着良好的命名。你需要给生产环境的代码、以及程序员,设计测试的接口,而程序员不得不解释异常的测试结果!

但是,如果你处置得当,测试就能胜过你所写过的任何文档。它们还不会过期,它们要么相关,要么没有使用价值。警告:即使你的测试达到了文档的良好水平,你也需要其它形式的技术文档!

你能把测试用作架构文档吗?你使用自动化测试,或许能够为架构的某些地方写文档。但是,良好的架构文档,除了记录了系统的较高层次的结构,还要记录你做出的所有决定、及其原因。

因此,为了真正地为架构写文档,你仍然需要额外的文档。Arc42 网站的模板算是好的开始。但要适度:保持文档简短,只增加绝对必需的东东!

当你找不到设计细节方面的文档(生产环境模块/函数,及其微测试)时,就会再一次破坏架构:你的架构文档会告诉你,在哪一个大致区域查找某个东东。但是,当你找到那片区域时,你需要整洁的代码、文档和测试,把你定位到精确的位置。

阐述重要性

我经历过很多难以维护的代码库(因为帮助客户优化难以维护的代码库,属于我提供的服务范畴)。据我经验看,带来大部分痛苦的根源不在于一些架构上的重大问题。诚然,它们往往也会带来很大的问题。

但是,大部分痛苦源于成千上万个微小的设计问题。某个方法的命名不合适;调用某个合作者(collaborator)的方法,而它应该在别的地方;臃肿的模块和函数;非独立的测试;命名不当的测试;在测试套件注3里,对生产环境代码做出一点点改动,就能破坏 10 个、20 个、或更多的测试。

有时候你不得不按时完成任务,随后再修复。但是,当你长期忽视设计时,那些小问题必定减缓你的进度。并且这种减缓在大多数开发人员还没有预料到时,就提前到来了。当你走了一次「捷径」之后,它就会在几星期、甚至几天、数小时内出现。

用微服务来拯救?

当我们做微服务时,还需要良好的内部设计吗?我们是为了替代、而非复用才写它们的,对吧?我们正按计划定期地去掉一部分吧……

嗯,看情况,主要取决于微服务的规模。就我目前看,关于微服务「正确」的一面,网上还没有一个结论……

如果你的微服务「够大」(相对来说)、属于垂直整合注4,你就应该确保内部设计的整洁。因为每一个「独立系统」的代码都十分庞大,只要一点点混乱,必定减缓将来的开发!

但是,如果微服务真地很小(仅有一少部分,由函数或类构成),那么你仍然要注意微小的地方。不过,某些小的设计方面的决定,就影响着各种微服务协同运行的方式,而非微服务内部的模块。

良好设计 != 良好架构

为了拥有良好的架构,你需要处理好细节。但是,具有了良好的、较低层次的设计,并不意味着拥有了良好的架构。

你不得不兼顾二者。但是,如果你想开始,就从设计着手:在代码的某一小块、甚至在培训中,你都能轻易练习。你可以重构越来越多的代码,使其具有良好的内部设计。当你熟练之后,再开始考虑更大的架构方面的事情:开始设计你的架构。

开始优化!

如果你愿意,现在就行动。测试驱动开发,看是否适合你。了解一些良好的设计,比如:SOLID 原则注5、简单设计的四个法则、耦合和内聚等……

但是,在某个阶段,你需要让整个团队处于同一节奏。你不得不向他们兜售更好的设计。然而,常常有人破坏你正在做出的设计优化。


作者简介

我叫 David Tanzer,从 2006 年起,一直做独立软件顾问。我通过培训、指导、以及为团队和个人提供咨询,来帮助客户正确地开发软件、以及开发合适的软件。更多详情,请移步我的网站


注释

  1. 在面向对象编程领域中,依赖反转原则(Dependency inversion principle,DIP)是指一种特定的解耦(传统的依赖关系创建在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99
  2. 滑坡谬误(Slippery slope)是一种非形式谬误,使用连串的因果推论,却夸大了每个环节的因果强度,而得到不合理的结论。滑坡谬误的典型形式为“如果发生A,接着就会发生B,接着就会发生C,接着就会发生D,……,接着就会发生Z”,而后通常会明示或暗示地推论“Z不应该发生,因此我们不应允许A发生”。https://zh.wikipedia.org/wiki/%E6%BB%91%E5%9D%A1%E8%AC%AC%E8%AA%A4
  3. 软件工程中的测试套件(test suite)有时也称为验证套件(validation suite),是许多测试用例的集合,测试用例可用来测试一程式是否正确工作,测试套件包括许多测试用例,一般也会有针对测试用例及其测试目的的详细说明,在进行测试时的系统组态资讯以及测试前需进行的步骤。https://zh.wikipedia.org/wiki/%E6%B5%8B%E8%AF%95%E5%A5%97%E4%BB%B6
  4. 垂直整合(Vertical Integration): 一个产品从原料到成品,最后到消费者手中经过许多阶段。如果一个公司原本负责某一阶段,当公司开始生产过去由其供货商供应的原料,或当公司开始生产过去由其所生产原料制成的产品时,谓垂直整合。http://wiki.mbalib.com/wiki/%E5%9E%82%E7%9B%B4%E6%95%B4%E5%90%88
  5. 在 程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期引入的记忆术首字母缩略字,指代了面向对象编程和面向对象设计的五个基本原则。https://zh.wikipedia.org/wiki/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1)
1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部