复杂性如何危害生产力——整洁代码的艺术(01)

发表于:2023-6-08 09:29

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:克里斯蒂安·迈尔    来源:51Testing软件测试网原创

  第1 章 复杂性如何危害生产力
  在这一章中,我们将全面了解复杂性这个重要而又探索极度不足的话题。复杂性究竟是什么?它在哪里发生?它会如何损害生产力?复杂性是精益高效组织和个人的大敌,所以值得仔细研究其存在的所有领域及存在形式。本章重点讨论复杂性问题,其余各章将探讨有效的方法,通过重新安排以前被其占据的资源来解决它。
  对于编程新手,有些问题可能复杂和令人生畏。我们来快速看看。
  选择编程语言。
  从数千个开源项目和大量现实问题中选出适合自己的。
  决定使用哪些库。(是用Scikit-lean还是NumPy?或者TensorFlow?)
  决定在哪些先进技术上投入时间——Alexa?应用、智能手机应用、基于浏览器的网页应用、Facebook或微信中的小程序应用、虚拟现实应用等。
  选择代码编辑器,如PyCharm、IDLE(integrated development and learning environment,集成开发与学习环境)或是Atom。
  这些复杂性来源会造成巨大混乱。“我如何开始”成了编程初学者的常见问题之一,这并不奇怪。
  最好的开始方式不是找本编程书,阅读该编程语言的所有语法特征。许多雄心勃勃的学生购买编程书,然后将学习任务添加到待办事项清单中——他们以为花钱买书,就会认真读。但就像待办事项清单上的许多其他任务一样,他们很少能读完编程书。
  最好的开始方式是找个实际代码项目——如果您是初学者,就找个简单的项目,然后推动它完成。在完成一个完整的项目之前,不要阅读编程书或网络上随便找的教程。不要在StackOverflow上滚动浏览无穷无尽的帖子。只需要设置好项目,用您拥有的有限技能和常识开始编码。我有个学生想创建一个金融仪表板应用程序,检查不同资产配置的历史回报,回答诸如“由50%股票和50%政府债券组成的投资组合的最大跌幅是多少”之类的问题。起初,她不知道如何实现这个项目,但很快就找到了Python Dash框架,该框架能够构建基于数据的网络应用。她学会如何设置服务器,而且只学了满足所需的超文本标记语言(HTML)和层叠样式表(CSS)来推进工作。现在她的应用程序已经上线,帮助成千上万人找到了正确的资产配置。但是,更重要的是,她加入了Python Dash开发团队,甚至还在与No Starch出版社合作,写一本关于Python Dash的书。她在一年内做到了这一切——您也可以。如果您不明白自己在做什么,也没关系,理解会逐渐加深,您可以只阅读能推动当前项目的文章。在完成第一个项目的过程中,会遇到以下高度相关的问题。
  用哪个代码编辑器?
  如何安装编程语言环境?
  如何从文件中读取内容?
  如何在程序中保存输入内容以供后用?
  如何通过控制输入来获得所需输出?
  通过回答这些问题,您将逐渐获得全面的技能组合。随着时间的推移,您将能够更好、更轻松地回答这些问题,您将能够解决更大的难题,您将建立起自己的编程模式和概念洞察力。即使是高级程序员也可以通过这个过程来学习和提高——只是项目变得更大、更复杂。
  通过这种基于项目的学习方法,您可能会发现自己得应对一些复杂性问题,比如在不断增长的代码库中寻找缺陷,理解代码组件和它们之间的互动,选择下一步要实现的合适功能,以及理解代码的数学和概念基础。
  复杂性无处不在,在项目的每个阶段都是如此。复杂性常常带来隐性成本:新手程序员颓然放弃,因为他们的项目永远看不到曙光。因此,问题来了:如何解决复杂性问题?
  答案直截了当:简化。在编码周期的每个阶段都追求简单和专注。如果您从这本书中只学到一件事,那就是:在编程的每个领域都要采取彻底的极简主义。在本书中,我们将讨论以下方法。
  梳理一天的工作,少做一些事,把精力集中在重要任务上。例如,与其同时开始10个有趣的新项目,不如选择其一,把所有精力集中在完成当前项目上。在第2章中,您将更详细地了解编程中的80/20原则。
  对于单个软件项目,摈弃所有非必要特性,专注于最小可行产品(见第3章),完成并发布它,高效、快速地验证您的设想。
  尽量编写简单精练的代码。在第4章中,您将学会如何做到这一点。
  少花时间与精力在过早优化上——非必要的代码优化是多余复杂性的主要来源(见第5章)。
  锁定用于编程的大块时间,避免分心,进入心流状态——心流是一个心理学研究的概念,指一种能提升注意力、专注程度和生产力的意识专注状态。第6章将专门讨论如何进入心流状态。
  实践Unix哲学,代码功能只针对一个目标(“做好一件事”)。第7章以Python代码为例,给出了Unix哲学的详细指引。
  在设计方案中贯彻简化原则,创建漂亮、整洁、专注、易于使用、符合直觉的用户界面(见第8章)。
  在规划事业发展、下一个项目、每天工作或是专业领域时,使用专注技巧(见第9章)。
  让我们更深入地研究复杂性概念,了解编码生产力的大敌。
  1.1  何为复杂性
  在不同领域中,复杂性这个词有不同的含义。有时,它被严格定义,如计算机程序的计算复杂性(computational complexity),提供了一种分析对于不同输入的特定代码功能的方法。其他时候,它被宽松地定义为系统组件之间相互作用的数量或结构。在本书中,我们将更广泛地使用后一个概念。
  我们如此定义复杂性。
  复杂性是由多个部分组成的,难以分析、难以理解或难以解释的一个整体。
  复杂性描述了一个完整的系统或实体。因为复杂性使系统难以解释,所以会引起挣扎和混乱。现实世界系统是混乱的,您会发现复杂性无处不在:股票市场、社会趋势、新出现的政治观点,以及拥有数十万行代码的大型计算机程序——如Windows操作系统
  程序员特别容易被过强的复杂性所困扰,例如本章将要涉及的这些不同来源的复杂性。
  项目生命周期中的复杂性。
  软件和算法理论中的复杂性。
  学习中的复杂性。
  各种过程中的复杂性。
  社交网络中的复杂性。
  日常生活中的复杂性。
  1.2  项目生命周期中的复杂性
  让我们深入项目生命周期管理的不同阶段:规划、定义、设计、构建、测试和部署(见图1-1)。
图1-1  软件项目的六个概念性阶段[依据电气电子工程师协会(IEEE)的官方软件工程标准]
  即使是非常小的软件项目,也可能要经历软件开发生命周期的全部六个阶段。请注意,每个阶段不一定只经历一次——在现代软件开发中,一般倾向于采用有更多迭代的实践方法,每个阶段都要经历多次。接下来,我们将看看复杂性如何对每个阶段产生重大影响。
  1.2.1  规划
  软件开发生命周期的第一阶段是规划阶段,有时在工程文献中被称为需求分析。这个阶段的目的是确定产品看起来是什么样子[ 原文如此。严格来说,规划阶段的工作是确定研发目的及其可行性。——译者注]。成功的规划带来一套被严格定义的所需功能,未来将交付给终端用户。
  无论您是自己做喜欢的项目,还是负责管理和协调多个软件开发团队,都必须弄清楚软件的最佳功能集合。必须考虑一些因素:实现一个功能的成本,不能成功实现该功能的风险,对最终用户的预期价值,市场和销售的影响,可维护性,可扩展性,法律限制,等等。
  规划阶段至关重要,因为它可以使您免于浪费大量精力。规划错误会导致价值数百万美元的资源浪费。另外,细致的规划可以为企业在未来获得巨大成功做好准备。规划阶段正是应用您新获得的80/20思维技能的时候(见第2章)。
  规划阶段也很难做得好,因为它具有复杂性。对以下因素的考虑增加了规划复杂性:提前正确地评估风险,弄清公司或组织的战略方向,猜测客户反应,权衡不同候选功能的积极影响,以及确定某个软件功能的法律影响。总的来说,解决这个多维度问题非常麻烦,让我们很难受。
  1.2.2  定义
  定义阶段将规划阶段成果转化为合乎规定的软件需求。换句话说,它将前一阶段的产出梳理成正式文档,以获得客户和以后使用该产品的最终用户的批准或反馈。
  如果您花了很多时间来规划和弄清楚项目需求,但却没有很好地沟通,这将会对以后造成很大的问题和困难。错误定义但有助于项目的需求可能与正确制定但没有帮助的需求同样糟糕。有效沟通和精确的规格说明对于避免歧义和误解至关重要。在所有的人类交流中,由于“知识的诅咒[ “知识的诅咒”是一个心理学术语,指别人学习我们已经掌握的东西,或是从事我们所熟悉的工作时,我们会倾向于错估他需要花费更长的时间。]”和其他与个人经验不符的心理偏见,信息传递成了相当麻烦的事。如果您试图把想法(或需求)从您的头脑传递到另一个人的头脑中,要小心:复杂性将是拦路虎!
  1.2.3  设计
  设计阶段的目标是起草系统的架构,决定提供所定义功能的模块和组件,并设计用户界面——同时牢记前两个阶段产出的需求。设计阶段的黄金标准是为最终软件产品的外观和构建方式创建清晰的图景,这适用于所有软件工程方法,敏捷方法只是在这些阶段中更快地迭代。
  优秀的系统设计者必须了解可用于建立系统的大量软件工具的优缺点。例如,有些库可能对程序员来说很容易使用,但执行速度很慢。构建自定义库对程序员来说更难,但可能会带来更快的速度,从而改善最终软件产品的易用性。设计阶段必须固定这些变量,使投入产出比最大化。
  1.2.4  构建
  构建阶段是许多程序员希望投入全部时间的地方。在这里,架构草案将转化为软件产品。您的想法将转变为有形的结果。
  通过前几个阶段的适当准备,很多复杂性已经消除了。理想情况下,构建者应该知道,应当从所有可能的特性中选择实现哪一些特性,这些特性是什么样子,以及可以用哪些工具来实现它们。然而,构建阶段总是新问题不断。有很多意料之外的事情,如外部库中的缺陷、性能问题、损坏的数据和人为的错误等,都会减缓进度。构建软件产品是高度复杂的工作,一个小小的拼写错误就会破坏整个软件产品的可行性。
  1.2.5  测试
  恭喜!您已经实现了所有特性,而且程序似乎能运行起来了。不过,您并没有真正完成。您仍然必须针对不同的用户输入和使用模式测试软件产品。这个阶段往往最重要——以至于许多从业者现在提倡使用测试驱动开发方法,即在没有写完所有的测试之前,您甚至不会(在构建阶段)开始实现功能[原文如此。实际上,测试驱动开发并不鼓励写完所有测试之后才着手写生产代码,而是要求在更小单元(如一个函数甚至一种参数输入)上用测试来推动代码的实现。——译者注]。虽然您可以反对这个观点,但一般来说,花时间创建测试用例来测试产品,检查软件是否为这些测试用例提供了正确的结果,会是一个好主意。
  例如,假设您正在实现使一辆汽车自动驾驶的程序,您必须写单元测试来检查代码中每个小函数(一个单元)在给定输入下是否产生了预期输出。单元测试通常会发现一些有问题的函数,这些函数在某些(极端)输入下表现得很奇怪。例如,考虑以下Python函数,它计算图像的平均红、绿、蓝(RGB)颜色值,也许可以用来辨别您是在城市还是在森林中旅行。
  例如,下面的像素列表产生的平均红、绿、蓝值分别为96.0、64.0和11.0。
  输出结果如下所示。
  虽然函数看起来很简单,但在实践中很多地方都会出错。如果像素列表被破坏了,有些RGB元组只有两个元素而不是三个元素怎么办?如果有一个值是非整数类型的呢?如果输出必须是整数数组,避免所有浮点计算所固有的浮点错误,又该怎么办?
  单元测试可以对所有这些条件进行测试,确保该函数能正常工作。
  下面是两个简单的单元测试,其中一个检查输入为零的边界情况,另一个检查函数是否返回了包含整数值的元组。
  结果显示,类型检查失败,函数没有返回正确的类型(即整数元组)。
  在更现实的环境中,测试人员会编写数百个这样的单元测试,检查该函数在接受每种类型的输入时是否产生如预期的输出。只有当单元测试显示该函数工作正常,我们才能继续测试应用程序的更高层级函数。
  事实上,即便所有单元测试都成功通过,测试阶段也还没结束。您必须测试各单元之间的正确互动,因为它们正一起构建更大的整体。您必须设计真实世界中的测试,驾驶汽车行驶数千甚至数万英里,发现在奇怪和不可预测情况下的意外行为模式。车辆在没有路标的小路上行驶会怎样?前车急刹会怎样?如果多辆汽车在十字路口互相等待呢?如果司机突然转向正在接近的车流呢?
  要考虑的测试太多,复杂程度太高,以至于很多人止步于此。理论上看起来不错的东西,即便第一次成功实施,也常常在应用于不同层面的软件测试(如单元测试或真实世界的使用测试)实践中失败。
  1.2.6  部署
  软件已经通过了严格的测试,是时候部署它了。部署可以采取多种形式。应用程序也许会发布到市场上,软件包也许会发布到存储库,主要(或次要)版本也许会公开发布。在更加迭代和敏捷的软件开发方法中,您会进行持续部署,多次经历部署阶段。根据具体项目不同,这个阶段需要推出产品、开展营销活动、与产品的早期用户交谈、修复暴露在用户面前后肯定会出现的新缺陷、协调软件在不同操作系统上的部署、排除不同种类的问题,或者持续维护代码库、适应新情况和改进代码。考虑到您在前几个阶段作出和实施的各种设计选择的复杂性和相互依赖关系,这个阶段可能会变得相当混乱。后面的章节将提出一些策略来帮助您解决这些混乱问题。
版权声明:51Testing软件测试网获得作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号