编写可维护的代码 (一)

本贴最后更新于 2415 天前,其中的信息可能已经渤澥桑田

可维护性我认为主要由两个方面构成, 一是可读性, 也就是代码要能让人看懂; 二是可调试性, 出了问题可以很快的找到原因. 市面上讲设计的书很多, 但大部分侧重于灵活性和可复用性, 比如面向对象设计和设计模式等. 灵活和可复用并没有什么错, 但我认为可维护要更重要一些, 试想如果一个模块非常灵活并被大量复用, 却不可维护, 岂不是不出问题则已, 一出就是灾难性的吗?

再看 C++ 语言, C++ 是一个提供了太多特性的语言, 每一件事情都可以用好几种可选的特性去实现, 但我们应该选择哪一种呢? 显然应该是最合适, 最实用的, 而不是最新最酷的.

从 05 年末到 09 年初, 我得到了一个非常难得的机会: 把一个项目做了两遍. 第一遍的时候用了很多 C++ 的高级特性, 也有意无意的引入了一个设计模式的思想, 项目也还成功, 但后续的维护却越来越难. 后来在做第二遍的时候, 受《UNIX 编程艺术》等的影响(也就是在这时候我成了把 C++ 当 C 用的程序员), 开始学习用最简单直接的方法解决问题. 而结果也相当好, 不光项目成功, 整个系统的可维护性也不错. 而且虽然设计时完全没考虑面向对象, 设计模式, 但最终的系统却又带着这些东西的影子, 只是实现方法和书上写的不完全一样(呵呵, 吹大了 欢迎大家对这一段扔几个西红柿鸡蛋之类的).

现在由于工作变动开始维护另一个系统, 这是一个很 C++, 很面向对象, 也很设计模式的系统, 可是维护起来却无比困难, 出问题后简直无从下手. 所以更体会到了可维护性的重要, 进而想到应该把自己的这点经验总结一下, 写出来. 目前总共有四五篇的题材, 都是很细节的问题, 希望自己能坚持写完. 我的方法也许不那么漂亮, 但应该还实用, 毕竟也算是真刀真枪的实战中总结出来的.

因为一发出来就被批了个体无完肤, 所以加上了这些文字, 说明一下背景. 不太喜欢口水仗, 后续的批评我将不再回复, 因为软件开发是工程而不是艺术, 工程讲究实用, 没有绝对的对和错, 一切都应该根据实际情况具体问题具体分析. 我写的东西只是供大家参考, 没有也无法强迫大家一定要用.

下面开始正题. 如果我们要实现一个类, 用于从流式缓冲区读出数据(典型应用是网络通讯中的数据包分析), 你会用下面哪种实现呢(错误处理用的是异常, 与主题关系不大, 故不详述)?

// A实现
class CBufferReaderA
{
 ...
 template<class T>
 CBufferReaderA& operator>>( T& v );
 ...
};

// B实现
class CBufferReaderB
{
 ...
 char ReadChar();
 short ReadShort();
 int ReadInt();
 ...
};

我想不少人会选择 A, 因为看起来更酷一些, 而且只写了一个模板函数就可以处理一大堆数据类型了, 但实际上, 如果从可维护性和实用性来说, B 却更好一点. 下面就来对比分析一下.

1. 像 cin/cout 一样, A 实现能把多个操作写在一起.

CBufferReaderA br;
br >> a >> b >> c >> d;

这一点 B 是作不到的, 因为它的返回值被用来返回实际读取的内容了. 可是当我们调试 A 实现支持的那一串代码时, 问题就出现了, 整个代码虽然是好几个函数调用, 但一下就执行过去了, 根本没法看到中间结果(VC 是这样, 其他调试器不清楚). 为了避免这个问题, 只好把这一串操作拆成单个的, 但这样一来 A 和 B 也就没什么区别了.

2. 如果需要跳过一段数据, 需要怎么做呢? 如果用 A 实现, 肯定是类似下面的方法:

int tmp;
br >> tmp;

而 B 实现则可以直接:

br.ReadInt();

对比可见, A 实现不光多用了一个没有什么实际用处的变量, 而且多写了一行代码. 只看这一点也许没多大问题, 但如果程序很大, 类似需求很多, 它带来的混乱就不可忽视了.

3. 缓冲区中是 char 型, 但我想用 int 保存读出的数据, 应该怎么办?

// A实现:
char c;
int i;
br >> c;
i = c
// B实现:
int i = br.ReadChar();

我想 B 的优势不用我说了吧.

4. 前面说到的 A 缺点也许还不算太严重, 下面这个应该就有足够的说服力了.

// A实现:
br >> a;
br >> b;
br >> c;
br >> d;
br >> e;

// B实现:
a = br.ReadChar();
b = br.ReadShort();
c = br.ReadChar();
d = br.ReadInt();
e = br.ReadInt();

看出问题了吗? 没错, 在 B 实现中, 我们很容易的知道每步操作从缓冲区中读了多少数据. 而如果用 A 实现, 这些信息就不那么明显, 必须去检查各个变量的定义, 也许你会说 VC 里面把鼠标放上去就能看到定义, 但也别忘了一次只能看一个, 而 B 则可统观全局. 如果是个很大的程序, 那 B 的可读性和可调试性要高很多.

5. 对 B 的一个批评是暴露了实现细节, 把读了几个字节清晰的写出来了. 但我认为这恰恰是它的优点, 因为只有应该隐藏的细节才需要隐藏, 而这里, 知道读几个字节对缓冲区分析来说非常重要的, 是不应该被隐藏掉的. 无限制的隐藏细节只会给自己找麻烦. 打个比方, 把路的细节隐藏起来, 方法之一是把眼睛蒙上, 我们又怎么走路呢?

6. B 相对于 A 也有一个缺点, 就是 A 可以通过重定义 >> 运算符, 让自定义类型和原生类型使用看起来完全相同的方法被读出来, 但一般来说, 这一点的艺术性远大于实用性, 而且考虑到前面所有的缺点, 它不足以成为我们选择 A 的理由.

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3453 回帖 • 201 关注
  • 技术

    到底什么才是技术呢?

    88 引用 • 179 回帖 • 4 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖 • 1 关注
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 52 关注
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 47 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    197 引用 • 547 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 1 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    107 引用 • 153 回帖 • 3 关注
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    28 引用 • 108 回帖
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    5 引用 • 26 回帖 • 526 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • 分享

    有什么新发现就分享给大家吧!

    247 引用 • 1792 回帖 • 7 关注
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 32 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖 • 1 关注
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖 • 1 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 622 关注
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖
  • gRpc
    11 引用 • 9 回帖 • 61 关注
  • Ngui

    Ngui 是一个 GUI 的排版显示引擎和跨平台的 GUI 应用程序开发框架,基于
    Node.js / OpenGL。目标是在此基础上开发 GUI 应用程序可拥有开发 WEB 应用般简单与速度同时兼顾 Native 应用程序的性能与体验。

    7 引用 • 9 回帖 • 388 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 584 关注
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 624 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 621 关注
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 124 关注