简单设计

设计缺乏一个统一标准

经常听到程序员们针对代码设计的一些讨论,A对B说:“我的这个设计用了策略模式和状态模式,假如后面客户会有这样的需求,可以无缝扩展,无比健壮。” B听着一脸狐疑,心中已经念叨了数遍 :"哼,这就是典型的过度设计",但B也不好说出口,因为上周才因为过度设计的分歧打了一次口水仗了。

我在项目中经常碰到这样的争论,因为设计的好坏本身没有一个标准权威的答案,这么多年各路大神提出的设计原则就够我们学几个学期,而且还是个似懂非懂。久而久之,每个人心中都有自己的一杆秤,导致了后来什么是好的设计就成了公说公有理婆说婆有理的问题,谁也说服不了谁。

设计是一个很大的话题,为了更好的起步,我尝试将范围缩小到敏捷团队程序员交付用户故事卡时的代码设计,不谈架构设计和系统设计。从变量、常量、方法、类、类与类之间的关系、对象的交互开始,一起来聊聊什么是设计的问题。

用简单的词汇来具象化设计

抽象的设计问题大大提升了初学者的学习门槛,想得太多怕被说过度设计,吃饱撑着没事找事。想少了,又怕被人认为能力不足,无脑编码。那到底怎么办,怎么样才能做出好的设计?SOLID、GoF的23种设计模式、STUPID、GRASP这些原则学会了就可以了吗?No,统统忘掉这些抽象不接地气的设计原则。起步,不要让自己那么难。来看看,极限编程领域的大神Kent Beck很早前就提出了几条容易理解的参考原则:

  1. 通过测试
  2. 揭示意图
  3. 消除重复
  4. 最少元素

简单设计原则不能忽略优先级

虽然Kent Beck提出的这四条原则里面也存在揭示意图这样一个每个人持有不一样标准的概念,但至少这一条在很大程度上能够代表代码的可读性,优秀程序员对代码可读性有90%相似度,可能在10%有钻牛角尖般的争议,也没有那么重要了。

其他几条则看起来就非常具体了,而且都能见字如意,测试一定要通过、重复就应该消除、元素没必要就不要存在。代码读不懂,那就就多找几个人看看。

通过测试不可动摇

通过测试 通常会被一概地理解为通过自己在项目中所编写的各种自动化测试,这么理解,也没有什么问题,但是需要满足两个前提条件:

  1. 测试覆盖率达到100%
  2. 所有测试都是有效的

如果你的项目中没法满足这两点,当然,99%的项目是做不到的(还有1%我只听过)。此时你需要换一个角度去理解 通过测试

想想你为什么写测试,测试在测什么,不就是为了增强你对系统满足了业务需求的信心吗?所以通过测试 广义理解为要满足业务需求,不论是自动化测试还是手工测试,你需要做的是满足业务需求,只不过我们推荐自动化能自动化的测试。

消除重复要全力以赴

重复乃万恶之源 -- SJ

重复意味着低内聚、高耦合,导致的后果是难以修改(霰弹式修改),必然降低系统对变化的响应力。响应力的降低势必会造成维护工作量的提升,我的简单设计价值观 一文中的懒惰 将驱使我尽我所能消除这些重复,从而减少修改时的工作量,提升软件的响应力。

揭示意图聚焦90%

对于揭示意图,我们只聊程序员那90%认同区间。可从一增一减两个方向同时去努力,从而达到揭示意图:

  1. 增强代码的自身可理解性,让代码自解释。
  2. 减弱代码的其他干扰因素,让代码更纯净。

不管是哪个方面,目的只有一个,回答一个问题:这个代码读者读起来更容易理解了吗?

那么在增强方面,比如在变量、方法以及类的命名等,我们都竭尽全力去赋予它们一个更加表达业务的名字,让它们能够自解释,从而让读者能够在深入细节之前就能够在较高层次上快速理解代码的意图。

减弱方面,比如在注释、方法的组织、类的交互设计等,可以去除不必要的注释,控制方法体的大小、降低类交互复杂度来让代码更纯净,从而让读者更好地聚焦在核心代码上。

最少元素说的是Less is More

既然说的是代码,那么充斥在你的代码库中的任何东西都可以理解是元素。当然,我们还是焦点聚焦在与代码相关的元素,比如,变量、常量、注释、注解、关键字、包。

最少元素 的核心思想是:在不必要的时候,尽可能减少代码元素来降低代码复杂度,保持简洁,贯彻less is more的思想,它道出了简单设计的精髓。

优先级让赋予四原则生命力

简单设计四原则给设计决策提供了有效的指导,在实际运用过程中,当面临冲突时,我们如何取舍,Kent Beck也给出了一个优先级顺序参考:通过测试 > 消除重复 >= 揭示意图 > 最少元素

以上四条优先级依次降低,这就话有点类似敏捷宣言中的最后一句:也就是说,尽管右项有其价值,我们更重视左项的价值

  1. 不可动摇(100%):通过测试
  2. 应当做到(95%):消除重复
  3. 尽全力做(90%):揭示意图
  4. 尽力去做(?%):最少元素

明白了优先级,你不再迷茫

通过测试,这条优先级最高的原则告诉我们任何时候我们编写的软件是要为客户创造真实价值的,如果为了消除重复、揭示意图或减少代码元素,而编写出不符合客户期望的软件,这就是好比捡了芝麻丢了西瓜。所以第一条没有满足的情况下,打着满足后三条的口号都是无稽之谈。

消除重复,一方面,已经存在的重复代码应当试图消除掉,降低修改的成本。另一方面,也在强调我们不应该为未来编码,即为一个尚未出现的重复或变化方向去增加额外的复杂度,比如建立一个抽象接口,却只有一个实现。

揭示意图,在我看来,跟消除重复 不相上下,绝大多数时候这两条是相辅相成的,不会因为消除重复而有损揭示意图,也不会通过引入重复增加揭示意图。在实际开发中我们应该尽量同时遵循这两条原则来提高软件的质量。

最少元素,这条优先级最低的原则告诉我们除非在增加了代码元素之后,能够消除重复或揭示意图或者通过测试,否则我们不应该增加代码元素。如果,你为了消除重复逻辑而抽取了一个公共方法,或者会为了揭示意图增加一个常量来替代魔鬼数字。你打着为了满足优先级更高的原则,而去违背优先级更低的原则,这个就符合了这个原则思想理念。

如果,在工作中你的领导的领导的领导的领导(4个人),当他们给你的指令有冲突是,你只用听更高级领导的指令。

示例:抽取公共方法

public class DateFormater {
    public LocalDate formatUserBirthday(String birthdayStr) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        return LocalDate.parse(birthdayStr, formatter);
    }
    public LocalDate formatRegisterDate(String registerDateStr) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        return LocalDate.parse(registerDateStr, formatter);
    }
}

public class DateFormater {
    public LocalDate formatUserBirthday(String birthdayStr) {
        return format(birthdayStr);
    }
    public LocalDate formatRegisterDate(String registerDateStr) {
        return format(registerDateStr);
    }
    private LocalDate format(String birthdayStr) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        return LocalDate.parse(birthdayStr, formatter);
    }
}

示例:常量代替魔鬼数字

public class Scheduler {
    public void executeJobs(int jobNumbers){
        if (jobNumbers < 1000000){
            for (int i = 0; i < jobNumbers; i++) {
                execute();
            }
        }
    }
}

public class Scheduler {
    public static final int MAC_CONCURRENT_EXECUTOR_NUMBERS = 1000000;
    public void executeJobs(int jobNumbers){
        if (jobNumbers < MAC_CONCURRENT_EXECUTOR_NUMBERS){
            for (int i = 0; i < jobNumbers; i++) {
                execute();
            }
        }
    }
}

针对揭示意图去除重复 这两条业界存在一些争论,觉大多数情况下,这两者并不冲突,在我的经验中,可能在一些测试用例中会通过引入重复来避免逻辑分隔等测试坏味道,从而对读者更加友好。Kent Beck也提出唯一让他有印象的冲突是发生在测试用例 [2]。


简单设计思想有更广阔的填空

Kent Beck 提出的简单设计原则更多关注的是代码设计,简单设计思想其实可以运用在架构设计、沟通协作上。

架构设计

  • 我们应该最先考虑的是满足业务架构的系统架构(通过测试,性能、稳定性等)
  • 借助DDD来合理的划分微服务(揭示意图,明确限界上下文)
  • 提取公共服务组件来分离关注点(消除重复,API Gateway、BFF等)
  • 最后,我们在满足了前三点的前提下尽可能简化系统架构中的组件

沟通协作

  • 在与客户正式场合的沟通中,我们始终应该明确沟通主题,确定目标(通过测试
  • 通过加强结构思考力来提升表达的结构性和清晰度,从而达到言简意赅(消除重复揭示意图
  • 最后,我们达到了前面三点之后尽量不说多余的废话

简单设计价值观甚至会影响你的生活方式,辅以断、舍、离的心态修炼,相信你的生活会逐渐变得简约而不简单。


注释

  1. 有关简单设计四原则更权威的表述,请参考Kent Beck的《Extreme Programming Explained: Embrace Change》

    • Runs all the tests
    • Has no duplicated logic. Be wary of hidden duplication like parallel class hierarchies
    • States every intention important to the programmer
    • Has the fewest possible classes and methods
  2. 参考Martin Fowler博客 BeckDesignRules

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

推荐阅读更多精彩内容

  • Everything should be made as simple as possible, but not ...
    _袁英杰_阅读 6,836评论 2 33
  • 博尔赫斯说:“写散文体的短文——寓言、神话、短故事——给了我某种神秘的满足。想起这些篇章,就仿佛想到硬币:实在、结...
    _张逸_阅读 2,375评论 0 2
  • 如果你认同 简单设计的价值观,我相信 简单设计原则 对你来说很容易理解并接受,它不像面向对象设计原则(比如:SOL...
    袁慎建阅读 867评论 0 2
  • 为了更好地阅读体验,欢迎访问博客原文 保持简单 简单是一个成年人司空见惯的词,我们大部分人却觉得纯真的孩子才是简单...
    袁慎建阅读 2,429评论 0 5
  • 注:手机推荐横屏观看:-) 幼儿园老师在给一群小朋友玩报数游戏,游戏规则如下:老师给定任意三个特殊个位数:3,5,...
    李永顺阅读 4,738评论 4 7