专栏首页炉边夜话拥抱变化—— 可扩展性杂谈

拥抱变化—— 可扩展性杂谈

拥抱变化—— 可扩展性杂谈

                                                                                      杨小华

作为软件开发人员最担心的就是变化,因为一旦变化,意味着自己的开发任务加重, 轻则修改代码,重则修改框架,如果不用做任何修改,则皆大欢喜,现实告诉我们,这是小概率事件,但比买彩票中大奖的概率还是大很多。于是各种讨论开始,开发人员开始讲述修改如何的大,进度如何紧张,架构师也在一旁不停的唠叨这个修改点的重要性,以及对整个系统带来的好处。

在业界曾经有一句很经典的话: “在软件开发领域中,唯一的不变就是变化 ” 。一旦变化,就有人遭殃,不是开发人员,就是设计师或架构师。无论谁遭殃,都不得不拥抱变化。

拥抱变化是极限编程( eXtreme Programming)里面一个非常重要的概念,代表了敏捷阵营对于变化的一种态度,那就是不拒绝,而且还主动求变。本文不想探讨敏捷方面的知识,如何去拥抱变化,而是想要探讨程序的可扩展性,如何在编码过程中,以最小的代价来应对程序未来的变化。

关于可扩展性, 其本身就是一个多方面的概念集合 。有人说程序的可扩展性必须建立在对未来需求的准确把握上,也有人说程序的可扩展性必须建立在能够对需求变化快速响应上。 不论 孰是孰非,其最终目的都是要求, 能在需求 发 生 变 化的 时 候以最小的代价去 应 付 变 化 。

可以从两个纬度对可扩展性进行讨论,一是设计可扩展性,二是编码可扩展性,前者从宏观上考虑,后者从微观上考虑,当然编码也是一种设计活动。本文重点论述编码的可扩展性,对于设计可扩展性,是一个系统性工程,由于作者还没有达到那个高度和境界,所以不敢瞎写,本文基本上不做介绍。

《 UNIX 编程艺术》一书中有一条关于扩展原则的描述:设计要着眼于未来,未来总比预想快。 关于设计可扩展性, 对于系统架构师或者系统工程师不仅仅要考虑在实现用户需求的基础上如何构建系统,还要考虑计算资源的可扩展、应用规模的可扩展,以及对技术换代的可扩展和性能等。

近期发生的干旱和水灾,每次都能找到人为的因素。本文开头提到的场景,如果进行代码回溯,也能找到一些人为的因素。如果当时的编码者在写代码时充分考虑了代码可扩展性,在一定条件下,可以达到用最小的代价去应对变化。如果当时只是为了完成任务,交差,后续的维护者可能面对的不是拥抱变化,而是拥抱痛苦!

场景一:在某嵌入式电信级设备整框分布式环境中,有 NEMI板(管理板), SWF板(业务板), STU板(业务板)和 LC板(业务板),每块板上都有 CPU,运行着各自的程序。目前的架构仅仅对 NEMI/SWF/STU板支持了 HA(High Available)功能,在 SWF卡上运行的某个业务,需要关注 SWF卡的主备倒换事件。 运行在 SWF卡上的程序可以收到来自 NEMI和 SWF卡的主备倒换事件,于是进行了如下编码:

void processSwitchEvent(GenMsg *pMsg) { 一些合法性判断语句 if(NEMI_SWITCH_EVENT == pMsg->getSwitchEventGrp()) { MSG_INFO(“Received NEMI Switch Event……”); return ; } //process SWF Switch Event 业务处理代码 }

可能开发人员在进行 if条件语句编码时,可能还考虑了另外一种写法:

void processSwitchEvent(GenMsg *pMsg) { 一些合法性判断语句 if(SWF_SWITCH_EVENT == pMsg->getSwitchEventGrp()) { MSG_INFO(“Received SWF Switch Event……”); 业务处理代码 } }

在最初的需求中,上下两种写法都是合适的,但是不是都合理呢?如果一旦需求发生变更, SWF卡上的另外一个业务需要关注 STU板的倒换事件,那么 STU板的倒换事件也会被广播到 SWF卡上,最糟糕的是,这两个业务都订阅了倒换事件(通过消息里面的内容来判断是哪块板发生了倒换),那么上下两种写法的区别就体现出来了,一个能正确运行,而另外一个会把 STU的倒换事件当作 SWF的倒换事件。不难看出,下面一种写法更具有可扩展性,达到了以最小的代价去应对变化。正是这样小的修改,往往会被忽略,隐藏一个很深的 bug,导致花大量的时间去定位。

对于上述的场景,大家编码时常会碰到,觉得这样写也合适,那样写也合适。虽然在一定条件下都很合适,但不一定都合理,那么此时就需要从其他方面加以考虑,如可扩展性,可维护性,可测试性等方面,从而确定哪种写法更合理。

场景二:假定存在如下一个消息类,最初类中只有一个成员变量,消息类的定义和实现如下:

class FsmFileTransferRequest : public GenMsgHdr { public: FsmFileTransferRequest (void) { memset (&mFileTransferReq, 0, sizeof(mFileTransferReq)); setMsgType (MTYPE_REQUEST); setMsgTypeQual (MQUAL_FSM_FILE_TRANSFER_REQUEST); setPayloadLen (sizeof(mFileTransferReq)); } // get/set operation …… private: SysPkg::FileTransferRequest mFileTransferReq; };

对于该消息长度,基类提供了两种接口,一个接口是 setPayloadLen (),另外一个接口是 setMsgLen (),该接口是更高一级的封装,为所传入参数减去基类消息的长度,最终结果还是消息的净荷长度。也许有人会说,基类就不应该提供两套函数,让人迷惑,出错在所难免。

由于场景变化或者需求变更,需要在该类中添加其他的成员变量,维护者可能是这个系统中的另外一个模块的开发者(自己所负责的模块中,构造函数里都是用消息总长度函数,默认其他开发者跟他一样),添加了成员变量和实现后,忘记修改消息的净荷长度,编译并运行,结果与预想的大相径庭,于是开始不停的打断点调试,不断的在怀疑消息是不是丢了,或者没有用修改的代码进行编译,总之,一切该怀疑的都在脑海中闪现了一遍。

或者,意识到要修改消息净荷长度,于是修改成:

setPayloadLen (sizeof(mFileTransferReq)+sizeof(mSuccessfulFlag));

如果只是一两个成员变量,还能忍受。需求一再变更,又增加了几个成员变量,继续修改, setPayloadLen()里面的代码会越来越长,只是代码写的难看而已。

如果类的实现者,在编写代码时,考虑一下可扩展性,采用消息的总长度函数,那么不论怎么添加成员变量,都不用修改消息长度,一劳永逸。如果确认这个消息不会被扩展,采用 setPayloadLen()也是合理的 。

通过以上两个例子可以发现,如果在编码时,充分考虑了编码可扩展性,即使需求发生变更,有时也可以达到事半功倍的效果。关键问题是如何识别出这样的场景,这个只能靠经验了,没有捷径可走!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 很幽默的讲解六种Socket I/O模型

    信息来源:幻影论坛     作  者: flyinwuhan (制怒·三思而后行)

    ternturing
  • JNI使用技巧点滴(二)

    作者:normalnotebook 背景<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com...

    ternturing
  • GCC内嵌汇编语言

    绝大多数 Linux 程序员以前只接触过DOS/Windows 下的汇编语言,这些汇编代码都是 Intel 风格的。但在 Unix 和 Linux 系统中,更多...

    ternturing
  • Hadoop和大数据两个世界是合并还是冲突?

    大数据文摘
  • Java集合中的LinkedHashMap类

      本文阅读最好先了解HashMap底层,可前往《Java集合中的HashMap类》。

    用户1148394
  • CentOS7环境下使用Cockpit创建KVM虚拟机

    yum -y install qemu-kvm qemu-kvm-tools qemu-img virt-manager libvirt libvirt-pyt...

    yuanfan2012
  • 深度学习笔记总结(2) 改善深层神经网络:超参数调试、 正则化以及优化

    如果我们的模型太简单并且参数很少,那么它可能具有高偏差和低方差。另一方面,如果我们的模型具有大量参数,那么它将具有高方差和低偏差。因此,我们需要找到正确/良好的...

    致Great
  • 电商后台系统产品逻辑全解析

    电商后台是业务要求较高的产品,当前台产品或业务人员提出需求时,有经验的后台产品经理第一时间想到的不是画原型、设计功能,而是分析要实现需求涉及哪些模块,需要协调哪...

    普通程序员
  • Ubuntu完全卸载mysql 转

    我们安装了mysql之后想卸载mysql时,往往是卸载不完全,导致下次安装又有问题,下面就提供ubuntu完全卸载mysql的方法.

    wuweixiang
  • 从零搭建docker私有仓库

    zhaoolee

扫码关注云+社区

领取腾讯云代金券