热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

专业程序员必知必会的技巧:驯服复杂代码

blog.csdn.nethzbooksarticledetails8646593你从入职第一天起就要应对复杂代码。若是还未遇到过无法理解的程序,那说明你编程的年头还不够长。在行业里,要不了多久你就会碰到让人发懵的混乱代码:巨兽、面条工厂、来自地狱的遗留系统。我曾接

http://blog.csdn.net/hzbooks/article/details/8646593 你从入职第一天起就要应对复杂代码。 若是还未遇到过无法理解的程序,那说明你编程的年头还不够长。在行业里,要不了多久你就会碰到让人发懵的混乱代码:巨兽、面条工厂、来自地狱的遗留系统。我曾接

http://blog.csdn.net/hzbooks/article/details/8646593

你从入职第一天起就要应对复杂代码。

若是还未遇到过无法理解的程序,那说明你编程的年头还不够长。在行业里,要不了多久你就会碰到让人发懵的混乱代码:巨兽、面条工厂、来自地狱的遗留系统。我曾接手过一个程序,它的前任在听说要增加一个分量不轻的新特性时,选择了辞职。(我并不怪他。)

软件系统的复杂度是不可避免的。有些问题就是很难,它们的解决方案很复杂。然而,你在软件中找到的大多数复杂度是我们自己造成的。在《The Mythical Man-Month》(人月神话)[Bro95]里,Fred Brooks将复杂度的两个来源分成必然(necessary)复杂度和偶然(accidental)复杂度。

这里有一种区分必然复杂度和偶然复杂度的思考方法:什么复杂度是问题域固有的?假设你面对的是一个日期/时间处理代码散落各处的程序。在处理时间时,存在一些必然复杂度:每月的天数不同,必须考虑闰年,等等。但多数我碰到的程序充斥着大量与处理时间相关的偶然复杂度:用不同格式保存的时间,加减时间的新奇(同时也是充满Bug的)方法,不一致的时间打印格式,说都说不完。

复杂度的死亡螺线

编程时常会遇到这种情况:产品代码库中的偶然复杂度渐渐压倒必然复杂度。情况在某一时刻会自我放大,我称这种现象为复杂度的死亡螺线,如图1所示。


图1 复杂度的死亡螺线

问题1:代码规模

构建产品时,它的代码规模最终将远超任何在学校或消遣项目中所遇到的。行业中的代码库的度量结果从成千到上百万代码行(Line of Code, LOC)不等。

John Lions在《Lions’ Commentary on UNIX 6th Edition》一书中写道:单个程序员能够理解和维护的程序大小的实际限制规模是1万行代码。于1975年发布的UNIX第6版的规模大约是9000行代码(不算机器特定的设备驱动程序)。

相比而言,Windows NT在1993年有4百万~5百万行代码。10年后,Windows Server 2003配备了2000名开发人员和2000名测试人员,他们管理多达5千万行代码。大多数行业项目并不像Windows那样巨大,但它们也都轻易地跨过了Lions设定的1万行代码的警戒线。这样的规模意味着公司内部没有人能理解整个代码库。

问题2:复杂度

随着代码规模的增长,最初想法的概念优雅性消失了。曾经对于车库中两个小伙水晶般清澈的想法变成了大批开发人员艰难跋涉其中的阴暗沼泽。

复杂度并不是代码规模的必然产物。大型代码库完全有可能被拆分成许多模块,其中每个模块都有清晰的用途、优雅的实现和为人熟知的与邻近模块的交互。

然而,即使设计良好的系统也会在它们变大时变得复杂。一旦没有一个人可以理解整个系统,这时多个人必须去理解系统中自己那部分—且没有人的理解跟其他人是完全一样的。

问题3:Bug

产品复杂度飙升,Bug也就不可避免地出现了。这是注定的—就算是伟大的程序员也不是完人。但每个Bug并非生而平等:高度复杂系统里的那些Bug尤其难觅踪迹。总是听到程序员说:“真搞不懂,伙计,系统刚刚崩溃了。”欢迎来到这糟糕的调试世界!

问题4:快速修补

问题并不在于产品是否有Bug—它肯定有,关键在于工程团队在出现Bug之后如何响应。在推出产品的压力之下,大多数程序员经常求助于快速修补。

快速修补是给问题打补丁,而非解决其根本原因。甚至常常不寻找根本原因。

程序员:在试图往网络队列中放入一个任务(job)且队列在10秒内无响应时,程序崩溃了。

经理:重试队列操作100次。

根本原因是什么?天知道,只要重试次数够多,你就可以掩盖任何问题。但如车身修补一样,某一位置的霸道胶水(Bondo)比实际残留的车本身部件还要多。

更难找的问题发生在补丁并没有解决问题根本原因的时候,问题通常根本没有消失—它只是转移到别处。在前面的对话中,重试100次可能很好地掩盖了问题,但万一需要101次重试怎么办?经理只是随便捏了个数字,这种膏药式修补只会让问题更难查。

沿着“快速修补”上行,我们现在得到了一个增加代码规模的完整闭环。

走向清晰

提起复杂的反面,人们通常会想到简单。但由于领域的必然复杂度,我们并不是总能写出简单的代码。应对复杂更好的方法是清晰。你是不是明白自己的代码要做什么?

明确两点会有助于我们减少软件偶然复杂度:清晰思考和清晰表达。

清晰思考

在分析问题的原因时,我们试图做出像“保存时间的方式应该只有一种”这样的清晰陈述。那为何UNIX C代码里还混杂着像time_t、struct timeval和struct timespec这样的结构呢?那并不是太清晰。

如何调和你的清晰陈述和UNIX计时功能的复杂度?你需要隔离复杂度,或将其抽象到单个模块中。在C里,这可能是结构体和操作它的函数;在C++里,它会是一个类。模块化设计让程序的其余部分可以用一种清晰的方式推导时间,而不用了解系统计时功能的内部机制。

一旦能将时间作为程序的一个单独模块进行对待,你也就能证明你的计时机制的正确性。完成这一工作的最佳方式就是单独测试,但是同行评审或书写规格说明也行。当一组逻辑是隔离的而不是内嵌在一大段代码体内时,它的测试和严格证明要容易得多。

清晰表达

随着你清晰地思考模块并将它与其余程序隔离,最终程序也就能更清晰地表达它的用途。处理问题域的代码应该真正专注于问题域。

将辅助代码抽出放入自己的模块之后,剩余逻辑读起来应该越来越像问题域的规格说明(虽然有更多分号)。

让我们看看前后对比。我已经无数次看到过如下这种C++代码:

[cpp] view plaincopy

  1. Time.cpp
  2. void do_stuff_with_progress1()
  3. {
  4. struct timeval start;
  5. struct timeval now;
  6. gettimeofday(&start, 0);
  7. // 干活,每半秒钟打印一条进度消息
  8. while (true) {
  9. struct timeval elapsed;
  10. gettimeofday(&now, 0);
  11. timersub(&now, &start, &elapsed);
  12. struct timeval interval;
  13. interval.tv_sec = 0;
  14. interval.tv_usec = 500 * 1000; // 500ms
  15. if (timercmp(&elapsed, &interval, >)) {
  16. printf("still working on it...\n");
  17. start = now;
  18. }
  19. // 干活……
  20. }
  21. }

循环的关键是“干活”部分,但在实际干活之前有20行的POSIX计时代码块。这并没有什么不对,但……就没有一种方法让循环保持对其问题域而不是对计时的关注吗?

让我们把所有时间代码放入它自己的类:

[cpp] view plaincopy

  1. Time.cpp
  2. class Timer
  3. {
  4. public:
  5. Timer(const time_t sec, const suseconds_t usec) {
  6. _interval.tv_sec = sec;
  7. _interval.tv_usec = usec;
  8. gettimeofday(&_start, 0);
  9. }
  10. bool triggered() {
  11. struct timeval now;
  12. struct timeval elapsed;
  13. gettimeofday(&now, 0);
  14. timersub(&now, &_start, &elapsed);
  15. return timercmp(&elapsed, &_interval, >);
  16. }
  17. void reset() {
  18. gettimeofday(&_start, 0);
  19. }
  20. private:
  21. struct timeval _interval;
  22. struct timeval _start;
  23. };

我们现在可以简化循环了:

[cpp] view plaincopy

  1. Time.cpp
  2. void do_stuff_with_progress2()
  3. {
  4. Timer progress_timer(0, 500 * 1000); // 500ms
  5. // 干活,每半秒钟打印一条进度消息
  6. while (true) {
  7. if (progress_timer.triggered()) {
  8. printf("still working on it...\n");
  9. progress_timer.reset();
  10. }
  11. // 干活……
  12. }
  13. }

计算机在上述两种情况下做的事情是相同的,但考虑第二个例子对程序可维护性带来的影响:
  • Timer类的测试和证明可独立于它在程序中的使用方式。
  • “干活”循环内的计时有了有意义的语义—triggered()和reset(),而不是一堆获取、增加和比较函数。
  • 现在对于计时的终止位置和(捏造的)循环实际起始位置都清晰了。

当工作在巨大丑陋的代码上时,依次考虑:这段代码想表达什么含义?它有没有办法说得更清楚一点?如果它是清晰表达的问题,你需要把那些碍事的代码段抽象出来,同前面展示的Timer类一样。若代码还是有点混乱,那可能是没有清晰思考的产品,需要在设计层面返工。

行动指南

聚焦于可被隔离和严格推导的一个编程方面,如计时。挖掘你正在从事的项目,识别出这样的代码段:若那部分逻辑被抽象到自己的模块,它能否表达得更清晰。

动手尝试更模块化的方法:选一组混乱的代码,分离必然复杂度和偶然复杂度。在这一刻不要操心细节,只看如何可以清晰地表达必要的业务逻辑,假设你有独立模块来处理支撑逻辑。

------------------------------------

本文节选自《程序员修炼之道:专业程序员必知的33个技巧》“技巧4:驯服复杂度”。

书名:《程序员修炼之道:专业程序员必知的33个技巧》

原书名:New Programmer's Survival Manual: Navigate Your Workplace, Cube Farm, or Startup

作者:Josh Carter

译者:胡键

页数:212

定价:49.00元

ISBN:9787111411642

豆瓣收藏:http://book.douban.com/subject/21323647/

PDF下载:http://vdisk.weibo.com/s/paFl8

当当购买:http://product.dangdang.com/main/product.aspx?product_id=23185207

内容简介:

这是每一位致力于成为专业程序员的软件开发新手都应该阅读的一本书。它是资深软件开发专家Josh Carter 20余年编程生涯的心得体会,从程序员成长的视角,系统总结和阐述了专业程序员在专业技能、编程工具、自我管理、团队协作、工作态度以及需要采取的行动等方面应该掌握的33个非常重要且实用的技巧。作者以自己以及身边的同事积累下来的经验、犯过的错误为素材,旨在为新人们引路,让他们在能力修炼的过程中少走弯路!

全书分为四个部分:第一部分(技巧1~14),从编程技能和工具使用两个方面总结了14个技巧,包含如何正确地书写代码、测试驱动设计、管理代码复杂度、改善遗留代码、代码评审、开发环境优化、自动化等;第二部分(技巧15~24),从自我管理和团队协作两个方面总结了10个技巧,包括如何树立自我形象、压力管理、建立良好人脉和高效会议等;第三部分(技巧25~30),介绍了典型高科技公司的组织结构以及你在整个公司中的位置,并且阐述了薪酬分配的问题;第四部分(技巧31~33),介绍了在日常工作中如何持续改善自己的工作和学习状态。

作者简介:

Josh Carter,资深软件设计师,具有超过20年编程行业从业经验。热衷于编程和追逐前沿技术,但同时谨记史蒂夫?乔布斯的箴言“真正的艺术家能让产品面市”。他还涉足工程管理领域,曾经主管大型企业软件开发团队。目前已出版多本关于计算机软件的技术书籍,同时他还在主流计算机杂志的技术专栏发表文章。

推荐阅读
  • 本文是一位90后程序员分享的职业发展经验,从年薪3w到30w的薪资增长过程。文章回顾了自己的青春时光,包括与朋友一起玩DOTA的回忆,并附上了一段纪念DOTA青春的视频链接。作者还提到了一些与程序员相关的名词和团队,如Pis、蛛丝马迹、B神、LGD、EHOME等。通过分享自己的经验,作者希望能够给其他程序员提供一些职业发展的思路和启示。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • windows便签快捷键_用了windows十几年,没想到竟然这么好用!隐藏的功能你知道吗?
    本文介绍了使用windows操作系统时的一些隐藏功能,包括便签快捷键、截图功能等。同时探讨了windows和macOS操作系统之间的优劣比较,以及人们对于这两个系统的不同看法。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • Win10下游戏不能全屏的解决方法及兼容游戏列表
    本文介绍了Win10下游戏不能全屏的解决方法,包括修改注册表默认值和查看兼容游戏列表。同时提供了部分已经支持Win10的热门游戏列表,帮助玩家解决游戏不能全屏的问题。 ... [详细]
author-avatar
mobiledu2502891413
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有