【开卷系列】代码整洁之道--程序员的职业素养

214 阅读21分钟

代码整洁之道.png

专业主义

担当责任

  • “专业主义”不仅象征着荣誉与骄傲,而且明确意味着责任与义务。专业主义的精髓就在于将公司利益视同个人利益。
  • 我曾因不负责任尝尽了苦头,所以明白尽职尽责的重要意义。
  • 为了按时交付软件,我没测试例行程序。我测试了系统的其他大部分功能,但测试例行程序要费好几个小时,而当时我又必须交付软件。因为故障修复部分都不涉及例行程序部分的编码,所以我也没担心会有什么不妥。结果加载新软件,例行程序运行失败,弄丢了客户一晚的数据,客户经理打电话来轰炸。

首先,不行损害之事

  • 软件开发太复杂了,不可能没有bug,但很不幸,这并不能为你开脱。人体太复杂了,不可能尽知其全部,但医生仍要发誓不伤害病人。如果他们都不拿“人体的复杂性”作托辞,我们又怎么能开脱自己的责任呢?
  • 所谓专业人士,就是能对自己犯下的错误负责的人,哪怕那些错误实际上在所难免。职业经验多了之后,你的失误率应该快速减少,甚至渐近于零。失误率永远不可能等于零,但你有责任让它无限接近于零。
  • 什么样的代码是有缺陷的呢?那些你没把握的代码都是!
  • 要进行百分百单元测试覆盖,这不是建议,而是要求!
  • 作为开发人员,你需要有个相对迅捷可靠的机制,以此判断所写的代码可否正常工作,并且不会干扰系统的其他部分。因此,你的自动化测试至少能够让你知道,你的系统很有可能通过QA的测试。
  • 所有软件项目的根本指导原则是,软件要易于修改,你必须能让修改不必花太高代价就可以完成。要想证明软件易于修改,唯一办法就是做些实际的修改。如果发现这些改动并不像你预想的那样简单,你便应该改进设计,使后续修改变简单。
  • 对每个模块,每检入一次代码,就要让它比上次检出时变得更为简洁。每次读代码,都别忘了进行点滴的改善。这完全与大多数人对软件的理解相反,他们认为对线上运行的软件不断地做修改是危险的。错!让软件保持固定不变才是危险的!如果不及时重构代码,等到最后不得不重构时,你就会发现代码已经“僵化了”。
  • 为什么大多数开发人员不敢不断修改他的代码呢?因为他们害怕会改坏代码!为什么会有这样的担心呢?因为他们没做过测试。

职业道德

职业发展是你自己的事。雇主没有义务确保你在职场能够立于不败之地,也没义务培训你,送你参加各种会议或给你买各种书籍充电。这些都是你自己的事。将自己的职业发展寄希望于雇主的软件开发人员将会很惨。

  • 了解你的领域
  • 坚持学习与练习
  • 学会合作
  • 教学相长
  • 了解业务领域

下面列举出了每个专业软件开发人员必须精通的事项。

  • 设计模式。必须能描述全部的24种模式,并且有实战经验。
  • 设计原则。必须了解SOLID原则,而且要深刻理解组件设计原则。
  • 方法。必须理解Scrum、精益、看板、瀑布、结构化分析和结构化设计等。
  • 实践。必须掌握测试驱动开发、面向对象设计、结构化编程、持续集成和结对编程。
  • 工具。必须了解如何使用UML图、DFD图、结构图、状态迁移图表、流程图和决策表。

说 “不”

能就是能,不能就是不能。不要说“试试看”。

  • 有许多项目搞砸,是因为开发人员没有坚决抵制各种不专业的需求(比如一些无关紧要但成本巨大的需求),抵制各种不专业的行为(比如为了赶工期而降低对代码质量的要求),最终只好喝下自己酿出的苦酒。
  • 有时候,获取正确决策的唯一途径,便是勇敢无畏地说出“不”字,我们要明白,委屈专业原则以求全,并不是问题的解决之道。舍弃这些原则,只会制造出更多的麻烦。
  • 花三分的力气去抵制无理的需求,可以节省十分甚至十二分的开发时间,相反,自欺欺人地说服自己凑活接受了无理需求,往往会非常被动乃至无法脱身,到最后,越挣扎,巨兽在泥潭中就会陷得越深。
  • 许诺“尝试”,就意味着你承认自己之前未尽全力,承认自己还有余力可施。许诺“尝试”,意味着只要你再加把劲还是可以达成目标的;而且,这也是一种表示你将再接再厉去实现目标的承诺。因此,只要你许诺自己回去“尝试”,你其实是在承诺你会确保成功。这样,压力就要由你自己来扛了。如果你的“尝试”没有达成预期的结果,那就表示你失败了。

说 “是”

说“是”时,你对自己将会做某件事做了清晰的事实陈述,而且还明确说明了完成期限。那不是指别人,而是指你自己。你陈述的是自己会去执行的一项行动,而且,你不是“可能”去做,或是“可能做到”,而是“会”做到。

  • 今天的程序员肯定得去面对诸如估算、确定最后期限以及面对面交流等沟通活动。做出承诺或许听来令人有点害怕,但它能够帮助程序员解决在沟通中可能发生的不少问题。如果你能够一直信守承诺,大家会以为你“是一名严谨负责的开发人员”。在我们这行中,这也是最有价值的评价。
  • 专业人士不需要对所有请求都回答“是”。不过,他们应该努力寻找创新的方法,尽可能做到有求必应。当专业人士给出肯定回答时,他们会使用正式的承诺,以确保各方面明白无误地理解承诺的内容。

编码

做好准备

编码是一项颇具挑战也十分累人的智力活动。相比其他类型的活动,编码要求更加聚精会神。因为编码时你必须平衡互相牵制的多种因素。

  • 首先,代码必须能够正常工作。必须理解当前要解决的是什么问题以及该如何解决。必须确保编写的代码忠实遵循解决方案。必须管理好解决方案的每一处细节,并且使语言、平台,现有架构以及当前系统的所有问题和平共处。
  • 代码必须能够帮你解决客户提出的问题。很多时候,客户提出的需求其实并没能真正解决他们自己的问题。这有赖于你去发现这些问题并与客户交流,以确保代码能够满足客户的真实需求。
  • 代码必须要能和现有系统结合得天衣无缝。你的代码不能让系统变得更僵硬、更脆弱、更晦涩,必须要妥善管理好各种依赖关系。简而言之,编写代码时必须遵循稳健的工程原则。
  • 其他程序员必须能读懂你的代码。这不仅包括要写好注释这类事,还包括要精心锤炼代码,使它能够表达你的编程意图。要做到这点很不容易。事实上,这可能是程序员最难精通的一件事。

同时要平衡好所有这些关注点颇为困难。长时间维持高度集中精神是有难度的。再加上在团队或组织中工作时常会遇到各种问题与干扰,以及需要留意和关注的各种日常琐事。总之,编码时无可避免地会受到各种干扰。

当你无法全神贯注地编码时,所写代码就有可能出错。代码中可能会存在不少错误,也可能会存在错误的结构,模糊晦涩,令人费解,无法解决客户的实际问题。总之,最终你可能必须返工修改代码甚至重写。在心烦意乱的状态下工作,只会造成严重的浪费。

如果感到疲劳或者心烦意乱,千万不要编码。 强而为之,最终只能再回头返工。相反,要找到一种方法来消除干扰,让心绪平静下来。

阻塞

有的时候,死活就是写不出代码来。我自己就曾经遇到过,也看到其他人身上发生过这种情况。干坐在电脑前面,但什么都写不出来。

这时候,你可以去找一些其他事情干。去查看邮件,去看朋友圈,去翻看些书,检查进度或者读点文档。也可能会去召集会议,或找其他人交流。你去做各种事情,这样便不必死盯着屏幕,干坐在那里。

调试

不管是否采用TDD或其它一些同等效果的实践,衡量你是否是一名专业人士的一个重要方面,便是看你是否能将调试时间降到最低。绝对的零调试时间是一个理想化的目标,无法达到,但要将之作为努力方向。

医生不喜欢重新打开病人的胸腔去修复此前犯下的错误。律师不喜欢重新接手此前搞砸的案子。经常重新返工的医生或者律师会被认为不专业。同样,制造出许多bug的软件开发人员也不专业。

保持节奏

软件开发是一场马拉松,而不是短跑冲刺。你无法全程一直以最快的速度冲刺来赢得比赛,只有通过保存体力和维持稳定节奏来取胜。无论是赛前还是赛中,马拉松选手都会仔细调整好自己的身体状态。专业程序员也会同样仔细地保存好自己的精力和创造力。

  • 知道何时应该离开一会儿:当碰到问题而受阻时,当你感到疲倦时,就离开一会儿,让富有创造力的潜意识接管问题。
  • 回家路上:我曾在回家路上,解决了许多问题。从问题中暂时脱离出来,十分有助于大脑以不同的但更具有创造性的方式搜求各种解决方案。
  • 洗澡:我也曾经在洗澡时解决了大量问题。也许是水流能够将我彻底唤醒,使我可以深入盘点之前大脑中浮现的所有解决方案。

进度延迟

管理延迟的诀窍,便是早期检测和保持透明。最糟糕的情况是,你一直都在告诉每个人你会按时完成工作,到最后期限来临前你还在这样说,但最终你只能让他们大失所望。不要这么做。相反,要根据目标定期衡量进度,使用三个考虑到多种因素的期限:乐观预估、标准预估、悲观预估。尽量严守这三个时间点。不要把预估和期望混淆在一起!把全部这三个数字呈现给团队和利益相关者,并每天修正这些数字。

帮助

编程很难,事实上,仅凭一己之力无法写出优秀的代码。即使你的技能格外高超,也肯定能从另一个程序员的思考和想法中获益。

  • 帮助他人。因此,互相帮助是每个程序员的职责所在。将自己封闭在格子间或者办公室里与世隔绝,有悖于专业的职业精神。你的工作不可能重要到你不能花一丁点儿时间来帮助别人。事实上,作为专业人士,要以能够随时帮助别人为荣。
  • 接受他人的帮助。如果有人向你伸出援手,要诚挚接受,心怀感激地接受帮助并诚意合作。不要死命护住自己的地盘拒绝别人的帮助。不要因为自己进度压力很大,就推开伸来的援手。不妨给他半小时的时间。如果到时候那个人不能真正帮到你,再礼貌地致歉用感谢结束谈话也不迟。要记住,如同要以乐于助人为荣一样,也要以乐于接受别人的帮助为荣。

辅导

辅导缺乏经验的程序员是那些经验丰富的程序员的职责。培训课程无法替代,书本也无法替代。除了自身的内驱力和资深导师的有效辅导之外,没有东西能将一名年轻的软件开发人员更快地提升为敏捷高效的专业人士。因此,再强调一次,花时间手把手地辅导年轻程序员是资深程序员的专业职责所在。同样道理,向资深导师寻求辅导也是年轻程序员的专业职责。

测试驱动开发

TDD(Test-Driven Development)是专业人士的选择。它是一项能够提升代码确定性、给程序员鼓励、降低代码缺陷率、优化文档和设计的原则。对TDD的各项尝试表明,不使用TDD就说明你可能还不够专业。

TDD的三项法则

TDD 的三项原则是:

  1. Red(红):编写失败的测试用例;

例如,在一个函数中,我们希望计算两个数的和。那么,在遵循TDD的原则下,我们首先需要编写一个无法通过的测试用例,比如输入2和3,期待输出结果为5,那么我们可以先写一个这样的测试用例:

def test_addition():
    assert(addition(2, 3) == 5)

此时,因为还没有实现addition函数,所以这个测试用例会失败。

  1. Green(绿):编写最少量的代码使得刚刚编写的测试通过;

接下来,我们需要编写最少量的代码,让测试用例能够通过。比如,我们可以这样实现addition函数:

def addition(a, b):
    return a + b

此时,我们再次运行测试用例,就会发现它已经通过了。

  1. Refactor(重构):重构代码以消除重复和不必要的复杂度。

最后,我们需要对代码进行重构,以优化其结构和性能,并删除任何冗余或不必要的代码。比如,我们可以将addition函数重构成这样:

def addition(a, b):
    return sum([a, b])

这段代码更简洁、可读性更好,并且执行效率更高。这就是TDD的三项原则,并且这些原则能够帮助程序员编写更加健壮、可靠的代码。

勇气

看到糟糕代码时,你为什么不修改呢?看到混乱的函数时,你的第一反应是:“真是一团糟,这个函数需要整理。”你的第二反应是:“我不会去碰它!”为什么?因为你知道,如果去动它,就要冒破坏它的风险;而如果你破坏了它,那么它就缠上你了。

但是如果你能确信自己的整理工作没有破坏任何东西,那又会是怎样一种情况呢?如果你拥有我刚才提到的那种把握,会怎样呢?如果你只需点击一个按钮,然后90秒内便可以确信自己的修改没有破坏任何东西,只是让代码变得更好了,那么又会是怎样的一种情况呢?

这是TDD最强大之处。拥有一套值得信赖的测试,便可完全打消对修改代码的全部恐惧。当看见糟糕的代码时,就可以放手整理。代码会变得具有可塑性,你可以放心打磨出简单而满意的结果。

设计

测试代码的一个问题是必须隔离出待测试的代码。如果一个函数调用了其他函数,单独测试它通常会比较困难。为了编写测试,你必须找到将这个函数和其他函数解耦的办法。换言之,测试先行的需要,会迫使你去考虑什么是好的设计。

TDD的优势

1. 简化代码设计

TDD可以帮助开发人员更好地理解业务需求,并在编写代码之前明确了代码的功能和需求。通过先编写测试用例,可以使开发人员专注于实现代码所需的最低限度的功能,从而简化了代码的设计。

2. 减少错误出现

由于TDD的核心是测试用例,因此它可以帮助开发人员及早发现代码中的错误。通过在编写代码之前编写测试用例,可以确保代码在实现期间不会出现明显的问题,从而减少了需要修正错误的时间和精力。

3. 提高代码质量

TDD可以帮助确保代码的可重复性、可维护性和可扩展性。由于每个测试用例都涵盖了代码的某个方面,因此可以确保代码在实现期间遵循最佳实践,并且代码中的边界情况得到了充分考虑。

4. 可以节省时间和成本

虽然TDD可能需要花费更多的时间在编写测试用例上,但它可以在开发周期的后期节省时间和成本。通过在实现期间及早发现问题并防止错误累积,可以大大降低项目的风险,并最终减少成本和时间开销。

TDD的局限

1. 需要更多的学习和实践

TDD需要一定的经验和技能才能正确实施。开发人员需要知道如何编写测试用例、如何处理测试失败和如何使用TDD工具。此外,在实践中也需要花费时间和精力来熟悉该方法。

2. 不适用于所有类型的项目

TDD非常适用于需要快速迭代和频繁更改的项目,但对于某些复杂或长期项目,TDD可能不是最合适的方法。某些类型的项目可能需要更全面的规划和设计,而TDD强调的是小步骤的迭代。

3. 可能需要额外的资源

TDD需要编写测试用例来覆盖代码,这可能需要额外的资源和时间。尽管这样做可以在项目后期节省时间和成本,但在实现期间可能需要更多的投入。

总之,TDD是一个强大的工具,可以提高代码质量和开发效率。但是,它需要开发人员在实践中不断完善和改进,并结合具体项目的需求和特点,以确定它是否适合使用。

练习

专业人士都需要通过专门训练提升自己的技能,无一例外。乐手练习音阶,球员练习绕桩,医生练习开刀和缝针,律师练习论辩,士兵练习执行任务。要想表现优异,专业人士就会选择练习。

任何事情,只要想做得快,都离不开练习。要想尽可能快的重复编码/测试过程,就必须能迅速作出决定。这需要识别各种各样的环境和问题,并懂得应付。

如果在两个习武者在搏斗,每个都必须能够迅速识别出对方的意图,并且在百分之一秒内正确应对。在搏斗时,你不可能有充足的时间来研究架势,思考如何应对。这时候,你只能依靠身体的反应。实际上,真正做出反应的是你的身体,大脑是在更高级的层面上思考。

无论是搏斗还是编程,速度都来源于练习。而且,两种练习并没有什么区别。我们选择了一系列的问题及其解决方案,一而再再而三地练习,直到烂熟于心。

无论如何,专业人士都需要练习。他们这么做,是因为他们关心自己能做到的最好结果。更重要的是,他们用自己的时间练习,因为他们知道保持自己的技能不落伍是自己的责任,而不是雇主的责任。练习的时候你是赚不到钱的,但是练习之后,你会获得回报,而且是丰厚的回报。

验收测试

验收测试是业务方与开发方合作编写的测试,其目的在于确定需求已经完成,软件符合用户需求和规格说明书的要求,以及满足用户的期望。

验收测试和单元测试是软件测试中的两个不同的概念,它们的主要区别如下:

  1. 测试范畴不同:单元测试是针对软件中的单个代码单元(如函数、方法等)进行测试,而验收测试是针对整个软件系统进行测试。
  2. 测试目的不同:单元测试旨在测试代码单元的正确性和稳定性,以及代码单元之间的交互是否正确,而验收测试旨在测试软件是否符合用户需求和规格说明书的要求,以及是否满足用户的期望。
  3. 测试方法不同:单元测试通常采用自动化测试工具进行测试,而验收测试通常采用手动测试方法进行测试。
  4. 测试时间不同:单元测试是在软件开发过程中进行的,验收测试通常是在软件开发完成后进行的。
  5. 责任主体不同:单元测试通常由开发人员完成,而验收测试通常由测试人员或最终用户完成。

测试策略

业务人员编写针对正常路径的测试(happy-path test),而由QA编写针对极端情况(corner)、边界状态(boundary)和异常路径(unhappy-path test)的测试。

自动化测试金字塔

image.png

单元测试、组件测试、集成测试和系统测试是软件测试中的四个重要阶段,它们分别如下:

  1. 单元测试: 单元测试是针对软件中的单个代码单元(如函数、方法等)进行测试,以验证它们的正确性和稳定性。单元测试通常由开发人员完成,测试方法一般采用自动化测试工具进行测试。
  2. 组件测试: 组件测试是针对软件中的独立模块(如类、对象等)进行测试,以验证它们的功能、性能和稳定性。组件测试通常由开发人员完成,测试方法一般采用自动化测试工具进行测试。
  3. 集成测试: 集成测试是针对软件中不同模块之间的接口和交互进行测试,以验证它们的正确性、稳定性和兼容性。集成测试通常由测试人员完成,测试方法一般采用手动测试和自动化测试工具相结合的方式进行测试。
  4. 系统测试: 系统测试是针对整个软件系统进行测试,以验证它是否符合用户需求和规格说明书的要求,以及是否满足用户的期望。系统测试通常由测试人员或最终用户完成,测试方法一般采用手动测试和自动化测试工具相结合的方式进行测试。

时间管理

会议

注意力点数

时间拆分和番茄工作法

要避免的行为

死胡同和泥潭

预估

压力

协作

团队和项目