编写可复用的服务端软件系统应该注意的五个重要细节

编写可复用的服务端软件系统应该注意的五个重要细节

作为程序员,我们往往希望自己写的代码能被最大程度的重用,但是我们依然能看到有很多“被重复发明的轮子”,其原因往往只是一个简单细节没有考虑到位。所以我就希望能总结一些这些容易被忽视的细节:

1 安装部署方面的细节

1.关于安装

很多软件进程、库的安装都比较繁琐,比如那些从源代码编译的软件,或者需要依赖很多第三方库的软件库,都会让使用者望而生畏。正确的做法应该是,把下载下来的压缩包,解压开就直接可以运行或者使用。例子有Eclipse软件。要做到这点,需要对于整体软件的依赖环境进行统一规划,否则自己都搞不清楚整体依赖状况,很难做到在压缩包里面去准备这些东西。现在最流行的Docker也是一个很好的解决方案。

2.关于配置文件位置

很多软件强大的配置软件让人印象深刻,比如Apache,MySQL,PHP这类,但是,他们都有一个共有的特点,就是都自带提供了默认配置。很多时候我们自己写的软件,常常会忘记在压缩包里面准备一份常用的配置文件,或者需要用户拷贝某个配置文件到某个指定目录才会生效。这样的配置文件操作往往让用户失去耐心。正确的做法应该是,在压缩包目录中,就带有了默认配置文件,解压开就直接应用这个配置文件。

3.多进程系统

作为分布式软件系统,往往都是多进程的。而用户去配置和启动多个进程,往往又是最复杂的。因为各个进程之间的关系需要在配置文件中正确登记,然后以正确的参数启动这些进程。有时候这种事情就连熟练的开发人员都一筹莫展。正确的做法是,提供一个可以在单机上直接运行的版本,作为默认的运行配置。这样用户只需要简单的启动操作,就可以完成功能的试用,然后在这个环境下,逐步尝试搬迁一些进程到其他服务器上。

4.集群系统的配置参数

我们往往喜欢把服务器IP列表作为配置文件放在系统中,但是这对于使用者来说是一个沉重的负担,因为如果写错了这些配置,往往只是会发现工作不正常,而不知道什么原因。对于部署这些系统的运维人员,在每次加入和退出机器的时候,修改这些IP也是个繁琐的工作。正确的做法应该是,用一个IP或域名来代表整个集群,而这个IP或域名的机器上运行集群的目录服务;集群中其他服务器,通过代码自动获得IP地址,上报给这个目录服务进程,从而获得整个集群的地址配置。这样复杂的集群配置就变得高度自动化,只需要配置一个中心节点地址即可。

5.错误提示

在陌生的软件启动出错,或者库初始化出错的时候,我们常常看不懂,甚至找不到出错信息。又或者完全不知道该如果处理。我们自己写程序也往往在出错的时候,就简单的描述出错的情况拉倒。但是,正确的做法应该是,对于启动、初始化错误,除了写日志外,应该在stderr打印一些文字,这些文字应该提出修正错误的可行方案。比如某个配置需要如何填写,某个进程需要先启动,或者使用某个发布包的脚本来修复。

6.运行环境

我们的软件在启动和初始化的时候,常常默认了环境是我们的预设环境,甚至是开发环境。比如我们会默认服务器安装了expect shell或者python shell。或者默认我们需要的进程已经启动,比如MySQL和Redis。或者某个共享内存已经建立好。这些依赖常常在不熟悉的使用者手上,难以自己去完全准备好。所以正确的做法应该是,尽量在启动脚本或者初始化过程中,尝试自己修复这些问题,比如自己去建立共享内存,启动所需的进程,安装需要的工具。这显然会增加很多开发量,但是这会大大提高产品的易用性。

2 关于配置参数

01

我见过很多系统,因为一个看起来不起眼的参数没配对就死活启动不了,或者修改了某个参数就运行失败。而这些复杂的参数对于我来说毫无意义,大部分都是一些基于实现的性能参数和预分配资源参数,这会让我非常恼火,在试用阶段就放弃使用。正确的做法是,为每个参数都配上默认参数。比如IP地址是取本地eth0网卡的地址,或者直接就是127.0.0.1。

02

我们往往系统于编写SHELL脚本来管理和控制我们的linux服务器系统。这会提供非常好的灵活些。但是有些系统的参数,必须要修改配置文件中的一个项目,这就让我们写的脚本异常复杂,需要用到sed或者其他一些文件处理程序来“生成”配置文件。正确的做法是,让命令行参数可以覆盖任何一个配置项目。这样我们就能简单的使用脚本来做任何事情。

03

当我们在用IDE,或者在开发机上开发时,对于编译和运行测试的环境来说,配置文件和命令行参数都一个不太方便的选择。我们可能要在某个机器上共享一些配置,或者要编写多进程并行的环境。正确的做法是,可以让环境变量来覆盖配置文件的任何一个项目,而命令行则可以覆盖环境变量的设定。这样我们就能拥有绝对零活的环境配置工具。

04

如果我们在开发一个库,而代码里面有很多需要输入的“常量”或者“配置项”,我们就要在初始化参数和配置文件中做选择。正确的做法应该是,根据这些输入“数据”所在的代码层次来决定。这些输入“数据”如果是这块代码的主要功能相关的,就应该成为函数中的参数,否则应该成为配置项。比如对于一个读取INI格式文件的库来说,文件路径应该是参数;而对于一个网络服务器来说,INI文件路径就应该是一个配置项。

3 关于日志的使用

01

我们写程序,在碰到检查出严重问题的时候,都会打印日志,但是往往忘了打印stderr,这样会造成用户不能立刻发现问题。所以我们应该在fatal级别以上的信息都同步输出一些到stderr中。

02

我们对于日志目录的设置,往往五花八门,有的是喜欢放到/var/log下面,有的则需要用户去配置。但是最容易找到的,是在软件的安装目录下的log/目录。这对于可重用的软件库尤为重要,我们往往因为无法简单的获得软件的安装目录,就放弃写到相对路径的log/下面。实际上我们甚至可以用/proc系统,或者getcwd,或者使用启动脚本来获得。

03

我们的Error和Warning日志往往代表了某种运行时的问题,而这些问题正是用户最关心的问题——运行状态。因此我们要在出现问题的时候,仔细规划这些输出,不要把一些无关紧要的事情也输出出来。而是最好应该把和系统的输入所产生的错误以Error, Warning输出。这样用户就能明确的知道问题的原因。

4 关于使用方法和接口函数API

01

我们提供的可重用方法,是通信协议,还是API库?这永远是个可以争论的问题,但是有一条是可以确定的:通信协议方便在不同OS和语言间使用,而API更方便具体的语言开发。我更倾向于首先提供API,因为这可以让用户自己对可重用代码的进程情况和内存情况做自定义的设置,这可以大大提高代码可以组合出的新功能。而API库可以提供比通信协议更多的语言特性,比如回调、继承、工厂等等。

02

对于可重用代码的使用方法,实际上有很多“流派”。但最糟糕的是没有“流派”,使用方法完全需要根据例程或者手册来用。这在纯C的函数库中是最常见的。比如文件操作,你需要先获得一个句柄,然后用这个句柄读、写,等等。JAVA对象的操作方法一般比较确定,就是调用构造器或工厂方法,获得一个对象,然后就可以自由的调用此对象的任何方法。一切调用顺序完全由各种“获得对象”的方法来规定,只要研究一下API手册就可以明白应该如何用。OC对象则稍微比JAVA复杂一点,需要用 alloc+init的双层调用来构建对象,以init()函数作为标准的工厂方法来设计使用顺序。相对的,很多C++的库也遵循这个原则,先初始化,然后调用Init()方法,差别之处在于你还需要想办法自己管理所有的对象和指针,因为大部分的函数都是用输出参数的方式返回结果。因此,不管怎样,应该让API在对象构建上有一个默认的规则,同时以构建方法API来规范用户的使用,这样可以显著降低用户的学习成本。

03

错误处理是影响可重用代码的一个重要细节。一般来说我们有三种手段对付错误:一是通过返回错误码,二是抛出异常,三是制造一次崩溃。对于JAVA或者其他虚拟机语言来说,异常是首选手段,因为可以记录调用栈,可以方便的从异常中恢复过来,同时不丢失异常信息。而对于C++程序来说,异常几乎没有什么用处,而使用错误码,则可能强迫所有调用带错误码的函数,都也返回错误码,而且需要每层调用都检查错误码,代码中遍布了各种if(ret == -1)之类的代码,影响可读性的同时,往往这种错误处理还没啥用处,因为最后不是往上继续返回这个错误码,就是打一行日志拉倒。所以在因为用户错误使用函数(参数、功能)的时候,还不如制造一次崩溃,这样可以让gdb来显示整个调用栈,从而提示用户的代码在何处错用了这个库。

04

名字空间的使用。名字空间虽然无法根本控制用户对预设代码的违反约定的调用,但是还是一个 有力的提示,比如二层空间(name1::name2::InnerClass)的代码是不希望被调用的。而且名字空间还可以用来标志软件的模块,这对用户也是一种 有益的提示。所以我们不要仅仅把名字空间看成是解决名字冲突的手段,而应该从对用户的教育角度上来使用它。

05

Doxygen文档。我们应该努力的维护这个文档,在所有的文档中,最需要投入精力的就是这些和源代码一起生成的注释(文档)。如果养成了随时修改这些注释的习惯,我们就无需额外去花时间整理API手册。当然这个文档的用处,也受本身API设计的限制。如果你的代码中充斥着大堆大堆没有规划过的函数,就算这些API文档再完善,也没有人能学会如何使用你的代码。正确的做法应该是先规划出供重用的接口,然后重点维护他们的注释,以便生成精悍有用的API手册。

5 软件架构

01

遵循分层原则,能得到灵活而且强大的接口。这需要我们的代码耦合是严格符合规范的:不跨层调用,不逆层调用,尽量重用自己的底层代码。要做到这些,需要反复考虑用户可能使用的场景,以及每一层,每一个模块的封装性。有时候权衡这些,比写功能实现更加折磨人。

02

对数据做抽象,建立结构模型。而不是使用指针+长度的buffer来解决一切。写C出身的人会自然的认为“一切都是数字”,所以只有数字的格式和数字内容的差别。而这种用法是难以提供清晰易学的接口的,因为人类天然的思维是对象式的,类型式的。否则C语言只提供一种char类型就可以了,反正所有的功能都可以用数字来自己完成。

03

为每个子模块都建立测试代码。这能让你检查自己模块的依赖性,如果测试代码太难编译成功或者难以在不同环境下运行,就证明了有易用性的问题需要解决。这是一种内省的开发方式,除了能减少BUG以外,还能切实以身作则的提供易用性,因为作者就是第一个使用它的人。

原文发布于微信公众号 - 韩大(handa1740168)

原文发表时间:2015-12-09

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开源优测

Robot Framework | 01 源码初探

概述 Robot Framework是一个通用的验收测试和验收测试驱动开发自动化测试框架(ATDD)。 它具有易于使用的表格测试数据语法,并使用关键字驱动测试方...

3578
来自专栏Seebug漏洞平台

Vivotek 摄像头远程栈溢出漏洞分析及利用

近日,Vivotek 旗下多款摄像头被曝出远程未授权栈溢出漏洞,攻击者发送特定数据可导致摄像头进程崩溃。

4557
来自专栏逸鹏说道

大公司都有哪些开源项目之腾讯

1.WeUI 为微信Web服务量身设计 WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信 Web 开发量身设计,可以令用户的使用感知...

4526
来自专栏较真的前端

关于如何做一个“优秀网站”的清单——基础篇

1545
来自专栏微信终端开发团队的专栏

安装包立减1M--微信Android资源混淆打包工具

上一篇文章我们讲述了Android减少安装包体积的一些tips,本文主要对前文提到的资源混淆做一个简单的分析。微信中的资源混淆工具主要为了混淆资源ID长度(例如...

3018
来自专栏宝哥的专栏

Docker系列学习文章 - docker API基本介绍和使用(十)

API这个词在维基百科里解释是这样的:应用程序接口(英语:application programming interface,缩写作 API),又称为应用编程接...

975
来自专栏CodeSheep的技术分享

前后端分离实践:基于vue实现网站前台的权限管理

2187
来自专栏数据之美

玩转 SHELL 脚本之:linux date 知多少?

最近好久没 update 了,一来是近期有点烦人的私事需要处理,二来是工作有点忙,业余时间还要整个 PPT,搜集素材啥的,非常耗时间。。。好吧,这都是借口,其实...

2057
来自专栏pangguoming

微信公众平台开发 自定义菜单

自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。开启自定义菜单后,公众号界面如图所示:

1331
来自专栏杨建荣的学习笔记

从MySQL源码看日志命令失效的原因

今天看数据库内核月报,发现一个蛮有意思的问题,就是show binary logs的时候没有任何结果,这个问题的原因很简单,但是分析问题的过程相比是艰辛...

2709

扫码关注云+社区