本文为开发小结-编程实践类下篇。linux
模块设计要明确职责,划分功能范围。c++
在设计较为通用的组件时,考虑到使用场景以及使用时的最低要求,对调用者有一些基本的入参要求,如不知足,则发出错误提示。这些额外要求,在函数注释中须要有明确注明,在内部实现时,配合适当的检测和提示机制。程序员
一个对外提供服务的控件,它的初始化函数须要的默认参数,最好在该组件的头文件中定义.
当参数不少时并且又没法避免时,每一个参数单独一行显示,同时仔细设计参数名称,努力作到见名知意。
对于默认参数的设置,外部须要关注的,每次变化的参数,放在第一位,其余不须要关注的,采用默认值就能够.编程
对于一个显示类控件来讲,它只须要接受外部的数据而且正常显示便可,至于这个控件的UI上的显示位置,应该由它的父窗口来决定。数组
对于业务相关的控件,须要考虑到业务灵活性,这类控件设计时要预留可变参数,不可只着眼与眼前的业务需求。好比显示行情信息为例子,需求给出显示五档行情,实际状况有一档行情、还有十挡行情,那么该控件对于行情显示数目可做为参数传入,内部动态显示特定挡位数的行情,确保灵活性和可扩展性。缓存
函数的注释,一行精简必要的解释,在不少状况下就足够,若是函数名称起的好的话,达到自注释的状况就更好。安全
如因业务规则须要,在某处须要特殊处理,那么在此处必定要加上 需求单号以及特殊规则的简要说明和文档出处,方便维护,既是方便本身查看,,也方便其余同事理解。数据结构
程序中必要的ASSERT
和相关的注释,要特别的保留,增长可维护性。ide
对于一些很差翻译成英文的业务,建议头文件中加上中文说明,方便其余同事查看。函数
注释规范,块注释建议采用多行的//
,而不是使用 /* */
,缘由有以下几点:
考虑linux平台上的代码对比工具diff
,若是用 /**/ 来作多行注释,从diff不必定可以看出来你是在修改代码仍是修改注释。
局部变量和成员变量的定义, 基本原则是:一行代码只定义一个变量,有利于用diff
查看出改了什么,有利于版本管理。
若是函数声明和函数调用中的入参个数大于3,那么须要在逗号后面换行,这样每一个参数占一行,便于diff
查看
与业务相关的数据结构定义以及配套的数据结构获取、设置、更新等,应该放在一块儿,保证相关业务以及对外接口的关联性,让别人看到这个定义,下面就能够看到相关的业务辅助函数。
在设计业务数据结构时,结合后台给出的接口文档给出基本的字段设计,举个例子,后台返回整型的币种信息,数据字典为1表明人民币,2表明港币,3表明美圆。此时的数据结构如何设计呢?
币种信息定义类枚举类型,
ECurrencyType { RMB = 1, HK = 2, US = 3 }
有以下如下几种方式:
方式一:
ECurrencyType m_nCurrency; // 保存由原始数据通过转换后的货币枚举类型 const char* GetCurrencyName(ECurrencyType eCurrencyType) // 用于将枚举类型转换为可读字符串,此转换函数与后台无依赖
方式二:
ECurrencyType m_nCurrency; string m_strCurrencyName; // 该成员经过 GetCurrencyName 转换获得的描述性文字,
方式三:
int m_nCurrency; // 保存后台返回的原始数据,具体显示完成交由上层界面去完成。 提供 `const char* GetCurrencyName(int nCurrency)` // 用于将枚举类型转换为可读字符串,此转换函数与后台有依赖
这三种方式,我都用过,使用次序依次是3,2,1,写的代码多了,对这些细微的考量有了更多的理解,这三种保存数据的方式,在不一样场景下有不一样的侧重,没法一律而论。字段映射的职责应该交由底层数据层,仍是交由上层UI层,具体状况具体分析。
对于类的初始化,应该所有设置为无效值,而不要一部分设置为无效值,另外一部分设置为默认值。在构造函数语义中,最好是设置为无效值,设置为默认值的操做,应该属于类的Init函数要作的事情。特别是对于接口定义的数据类,初始化过程当中,若是由默认值的设置,那么在后续填充发送字段时,该默认值字段要不要再设置一次就存在不肯定性,这里可作可不作,从业务上来没问题,但从可维护性上,后来接手的人看到这里时,内心要多一个心眼,要去看下构造函数才知道要不要设置,增大心智负担。
构造函数里面的初始化,要作无效的初始化,整型统一置为0,指针设置为NULL等,和业务相关的初始化,建议放到单独的Init函数中去作,好比读取配置信息,加载相应的资源,设置某些变量的默认值等等。
在含有众多成员变量的结构体中,如何快速清空,能够采用 memset(this, 0, sizeof(TData))
的方法来清空,该方法使用的前提条件是成员中无stl类型变量。
另外一种方法是提供独立的函数(Reset(),Clear())来进行复位,该函数既能够在构造函数中使用,又能够在其余地方使用。
重构必定要从最小模块出发,切记一次搞定全部,保证任务的聚焦性和重构目标的可解决性是最重要的。
过早的优化是万恶之源,在一个正确的代码上让它跑的更快的难度远低于在一个跑的更快的代码上让它正确。
重构的好,会增长代码的可维护性,重构的很差,则会增长维护成本、测试成本。因此,重构的首要目标是加强代码的可维护性,至于对性能的影响如何,是加强了性能仍是下降了性能,在重构时,不该过多关注。跑的对优于跑的快。经过代码来改善性能只是一个途径而已,做为追求卓越的程序员,应该要了解不少在代码以外的行之有效的改善方法,而可维护性基本上只能靠代码自己,而改善性能,除了代码自己以外,还有更多更加有效的方法。
针对 if-else 类型的代码重构整体思路:尽量维持正常业务流程代码在最外层, 减小if-else嵌套。
具体的手法有:
对于一些基础组件的重构改进,不建议直接在原有公用组件上进行修改。较好的流程是
解除耦合的方法:
若是是进行组件以及系统的重构,工程实践上最好从主分支拉出一个重构分支,在重构分支上的提交的同时,按期合并来自主分支的改动。重构须要划分模块,小步快跑,同一类型的重构修改按一次提交。对于已经通过测试的代码,不要贸然去修改,较好的方法时,用一个全新的实现去逐步代替旧版实现。从流程上来讲,重构的测试,更多的靠开发人员的单元测试,如在重构过程当中,有修改实现流程方面的动做,必定要在提交备注中写明并告知测试。
业务实体一致性重构,类、函数、页面、组件,各自内部要作到足够的语义化,同一类型的数据定义,其名称在整个工程中要保持一致性,要作到这点,就必须针对具体的业务特征制定规范,什么业务使用什么类型的核心词汇来描述之类的。该规范由团队成员共同制定,在此举一个例子:好比说:股东代码,从业务上来看,它应该是一个集合概念,内部包含代码、市场、货币类型。
class CHolder { CString m_strHolderCode; // 股东代码 char m_cExchange; // 市场类型 ECURRENCY m_eCurrency; // 货币类型 }
外部用到股东的地方,核心命名为Holder
,好比成员变量可用m_vtHolder
,局部变量用Holder
等,无论在哪里,核心词汇均为Holder
。
现有工程中有基础工具类函数,内部经过封装底层函数来实现,本身看到时会思考,为何不直接使用底层函数,而采用封装一层呢?列出本身遇到的坑,以获取程序当前所在目录这个功能为例子,系统级API有提供,基础工具类也有提供相关的函数,那么在使用时,选哪种呢?
GetCurrentDirectory 得到当前进程的工做目录。若是进程处于调试状态,那得到的目录为调试器给该进程设置的工做目录,默认为$(ProjectDir),即为包含该项目文件的目录。若是处于运行状态,则得到的是当前的工做目录。这里要特别注意:当以快捷方式启动时,该函数返回的路径是配置在快捷方式中的起始位置编辑框中,而该属性能够人为修改,而且不会影响直接双击启动时得到的路径。所以,使用使用该函数来获取时,在Debug和Release中,在测试时,会返回不一样的值,这个函数存在不肯定性。
GetModuleFileName: 得到可执行程序所在的可执行文件路径,与是否处于调试状态无关,推荐使用。
类的初始化列表,遵循一行一个的原则,而且初始化顺序和在类中定义的顺序保持一致,若有新增成员变量,也要对应保持一致增长。
浮点数统一使用 double,不要使用float。
无论是针对简单类型,仍是复杂类型, 优先使用++i,而不是i++,减小心智负担。
永远不要在头文件中使用 using namespace,这会致使全部包含该头文件的文件都隐式此命名空间,形成命名空间污染。在cpp实现文件中使用using std::XXX,须要哪一种类型,就引用哪一种类型。
使用 std::array 或者 std::vector来代替C风格的数组
c++风格的cast(dynamic_cast 或者 static_cast)能够提供更多的编译器检查和安全特性,用于替代c风格的cast。
c++11 中新增了 override 关键字,用于重载虚函数时检查函数签名一致性,virtual用在基类中,用于标示该函数是虚函数。在stackoverflow中的有一个很好的解释:
基类须要virtual关键字来声明虚函数,在派生类中,函数成为虚函数的方式是具有同基类同样的签名类型,override关键字指示编译器来检查在派生类中修饰的函数是否在基类中有相同的签名函数。若是在派生类中重载虚函数时加上virtual,那如何肯定该virtual修饰的函数是重写父类的函数,仍是该派生类自身提供的虚函数? override关键字正是用在这个地方,取消重写的歧义性。假如父类的维护者给该虚函数新增了一个默认参数,那么带有override修复的派生类能够经过编译器来检查是否有影响.
所以,综上所述:在基类声明中,使用virtual来修饰虚函数声明是必要的。在派生类中,无需再次添加virtual来修饰,而是经过 override 修饰符来加强可维护性。
不只仅是简单完成基本需求,而要在此之上,看有哪些操做是能够避免的,哪些操做是能够延时的,哪些指令是能够推迟发送的,哪些指令返回的数据是能够缓存使用的。
想到这里,以一个实际开发的一个功能来举例:在价格框中价格变化后,须要主动查询最大可交易数量。
这个功能,实现起来不难,难在如何更优雅、更高效的实现。这里有几个前提,价格框能够手工输入价格触发改变,也可由程序自动设置价格。
在实现时,有两种方式
第一种方式是: 在每个价格框改变的地方,在后面手动调用请求最大可交易数量的函数。
优势:逻辑直接,价格变化后请求数据
缺点:改变价格框的状况较多时,容易遗漏
第二种方式是:价格框改变时,由价格框发消息通知父窗口,在父窗口中经过消息响应来处理价格变化以后的请求。
优势:分离变化事件的产生和处理,从逻辑上解耦,在须要新增响应变化时,改动的地方较少,可控。
缺点:这种方式,对于开发人员来讲,不必定容易直观想到,须要较好的程序组织能力才能想到。
第三种方式,在第二种方式基础上,在价格框内部增长进一步的细节处理,记录上一次的价格,当修改后的价格与上一次价格不一致时,才发出价格改变的消息,若一致,则不发送消息,进一步优化效率,而随着而来的代码会更加复杂些。
第一种是很容易想到的处理方式,我当初在作这个功能时,第一时间浮如今脑海中的是第一种方式,后来随着和同事商量,才有了后面几种方式的思路。要作到高效开发,首先要有一颗精益求精的心,其次要认清软件开发的事实,最优方案或者说最优实现是不可能一蹴而就的,软件开发是一个不断迭代的过程,无论是什么水平的开发人员,第一次作新功能时,想要一次性作到极致完美,一次性经过全部的测试案例,是很是难的。咱们要认清这一点,在这一前提的基础上,面对需求作好一个设计后,不要着急着去动手实现,而是多和同事讨论下,看有没有更好的思考问题的角度和解决方案。
针对UI界面类的交互开发,团队必定要制定统一的交互流程,在需求讨论过程当中,一些没有提到的点和关联逻辑,就按照通用流程来走。举个例子,
打开界面进行初始化时,哪些数据要清空,哪些控件要复位,复位的规则是什么,都要一一肯定好。
若某个编辑框中内容变更,会触发哪些后续流程,数据回来后,要更新那些内容,焦点定位在哪里等交互问题。
操做的有效性校验的提示内容以及提示方式等,确认错误后的复位逻辑等?
若是产品需求中没明说,那么在实施时,就和已有的流程一致。若是产品需求由特别规定,就严格按照产品定的流程来作。