视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
专业程序员必知必会的技巧:驯服复杂代码
2020-11-09 07:23:13 责编:小采
文档

根本原因是什么?天知道,只要重试次数够多,你就可以掩盖任何问题。但如车身修补一样,某一位置的霸道胶水(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:97871114112

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

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

    下载本文
    显示全文
    专题