代码中的软件⼯程⼯程化编程实战
代码中的软件⼯程⼯程化编程实战
C/C++编译调试环境配置
Click the Extensions view icon on the Sidebar (Ctrl/⌘+Shift+X).
Configuring VS Code:
tasks.json (build instructions)
launch.json (debugger settings)
c_cpp_properties.json (compiler path and IntelliSense settings)
代码最初是如何⽣长起来的?
UML三巨头之⼀的Ivar Jacobson曾说“银弹不存在,我们需要的仅仅是明智的软件开发⽅法( smart software development ),软件必须从⼀个⼩的可运⾏的skinny system 开始,逐渐充实⽣长称为full-fledge 的成熟系统。”
我们就采⽤这个思路,从hello world 开始不断迭代调试使代码长的越来越像⼀个命令⾏的菜单⼩程序。写代码要⼩步快跑不断迭代,罗马不是⼀天建成的,不要期望⼀撮⽽就。
另外需要说明的是,做实际项⽬并不⿎励⼀开始就从头开始写代码,⽽是已有的类似项⽬做对⽐分析,对开源代码做逆向⼯程和再⼯程,对项⽬有深刻理解的基础上,再考虑是从头构建还是维护⼀个已有的项⽬来达成⽬标。
简约⽽不简单——代码规范和代码风格
代码风格的原则:简明、易读、⽆⼆义性
⼀个项⽬代码的风格就如同⼀个⼈给⼈的印象,代码风格之所以那么重要,是因为它往往决定了代码是否规范、是否易于阅读。
代码虽然最终是要给机器看的,但毕竟还是⾯向程序猿们的编程,程序猿们是要陪伴整个项⽬开发过程的。在编写代码的过程中,尤其是在协作开发的过程中,如果对⽅的代码杂乱⽆章,读起来都费劲,更别说还需要在此基础上进⼀步开发,这对程序员来说是个巨⼤的挑战。
好的代码风格不仅易于代码的阅读和理解,还能在很⼤程度上减少⼀些不必要的语法错误,例如少了"}" ,如果在编码的时候严格遵循了花括号的对齐规则,那此类错误将容易被避免。
到底什么样的代码是好代码呢?
⼀是规范整洁。遵守常规语⾔规范,合理使⽤空格、空⾏、缩进、注释等;
⼆是逻辑清晰。没有代码冗余、重复,让⼈清晰明了的命名规则。做到逻辑清晰不仅要求程序员的编程能⼒,更重要的是提⾼设计能⼒,选⽤合适的设计模式、软件架构风格可以有效改善代码的逻辑结构,会让代码简洁清晰;
三是优雅。优雅的代码是设计的艺术,是编码的艺术,是编程的最⾼追求。
代码风格规范总结
缩进:4个空格;
⾏宽:< 100个字符;
代码⾏内要适当多留空格,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等⼆元操作符的前后应当加空格。
在⼀个函数体内,逻揖上密切相关的语句之间不加空⾏,逻辑上不相关的代码块之间要适当留有空⾏以⽰区隔;
在复杂的表达式中要⽤括号来清楚的表⽰逻辑优先级;
花括号:所有‘{’ 和‘}’ 应独占⼀⾏且成对对齐;
不要把多条语句和多个变量的定义放在同⼀⾏;
命名:合适的命名会⼤⼤增加代码的可读性;
类名、函数名、变量名等的命名⼀定要与程序⾥的含义保持⼀致,以便于阅读理解;
类型的成员变量通常⽤m_或者_来做前缀以⽰区别;
⼀般变量名、对象名等使⽤LowerCamel风格,即第⼀个单词⾸字母⼩写,之后的单词都⾸字母⼤写,第⼀个单词⼀般都表⽰变量类型,⽐如int型变量iCounter;
类型、类、函数名等⼀般都⽤Pascal风格,即所有单词⾸字母⼤写;
类型、类、变量⼀般⽤名词或者组合名词,如Member
函数名⼀般使⽤动词或者动宾短语,如get/set,RenderPage;
注释和版权信息:注释也要使⽤英⽂,不要使⽤中⽂或特殊字符,要保持源代码是ASCII字符格式⽂件;
不要解释程序是如何⼯作的,要解释程序做什么,为什么这么做,以及特别需要注意的地⽅;
每个源⽂件头部应该有版权、作者、版本、描述等相关信息。
编写⾼质量代码的基本⽅法
通过控制结构简化代码
代码的基本结构分为顺序执⾏、条件分⽀和循环结构,还有很多语⾔中⽀持的递归结构。我们要利⽤代码的基本结构特点来有效地梳理需求,从⽽写出思路清晰的代码。
通过数据结构简化代码
⼀定要有错误处理
程序的主要功能(80%的⼯作)⼤约仅⽤20%时间,⽽错误处理(20%的⼯作)却要80%的时间
性能优先策略背后隐藏的代价
传统上由于CPU计算资源和存储资源较为昂贵,在编写代码时往往更多地考虑最⼤限度地⾼效利⽤计算机资源,因此在编写代码的习惯上追求性能优先的策略。但是随着计算资源的硬件成本逐步降低,尤其是云计算技术的发展计算资源的价格⼤幅度下降,性能优先的策略背后隐藏的代价逐步显露。
cost to write the code faster。当软件⼯程师的⼈⼒成本远⼤于所消耗的计算资源成本时,提⾼代码编写的⼯作效率将更有价值;
cost to test the code。质量保证的⼈⼒成本和质量保证的成效也⽐所消耗的计算资源成本更有价值;
cost to understand the code。性能优先的策略往往会让代码很难理解,结果需要消耗更多的⼯时;
cost to modify the code。⾯向机器的代码修改起来更困难,可扩展性差,同样会消耗更多⼯时。
因此,我们在具体编程实现过程中已经不再需要考虑代码性能问题,将更多精⼒放在提⾼⼯作效率、质量保证、代码的可读性、可扩展性等⽅⾯,让性能问题在更⾼层的软件架构设计层⾯考虑更加合理有效
拒绝修修补补要不断重构代码
如果您觉得控制流程盘根错节、判定过程难以理解、或者⽆条件的分⽀难以消除,那么就该重新返回
到设计了。重新检查设计,搞清楚您遇到的问题是设计中的固有问题,还是设计转化为代码的过程中引⼊的问题。
返回设计重新思考设计,使得设计结构和代码结构在逻辑上保持⼀致,⽽不是“头痛医头脚痛医脚”的⽅式对代码修修补补。不断重构代码是编写代码的基本⽅式。
模块化的基本原理
模块化(Modularity)是在软件系统设计时保持系统内各部分相对独⽴,以便每⼀个部分可以被独⽴地进⾏设计和开发。这个做法背后的基本原理是关注点的分离(SoC, Separation of Concerns),
从⽽整个软件系统也更容易定位软件缺陷bug,因为每⼀个软件缺陷bug都局限在很少的⼀两个软件模块内。
整个系统的变更和维护也更容易,因为⼀个软件模块内的变更只影响很少的⼏个软件模块。
软件设计中的模块化程度便成为了软件设计有多好的⼀个重要指标,⼀般我们使⽤耦合度(Coupling)和内聚度(Cohesion)来衡量软件模块化的程度.
耦合度(Coupling)
耦合度是指软件模块之间的依赖程度,⼀般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和⽆耦合(Uncoupled)。
⼀般在软件设计中我们追求松散耦合。
内聚度(Cohesion)
内聚度是指⼀个软件模块内部各种元素之间互相依赖的紧密程度。
理想的内聚是功能内聚,也就是⼀个软件模块只做⼀件事,只完成⼀个主要功能点或者⼀个软件特性(Feather)。
模块化代码的基本写法
命令⾏菜单在开源社区中常见的写法
将数据结构和它的操作与菜单业务处理进⾏分离处理,尽管还是在同⼀个源代码⽂件中,但是已经在逻辑上做了切分,可以认为有了初步的模块化。
进⾏了模块化设计之后我们往往将设计的模块与实现的源代码⽂件有个映射对应关系,因此我们需要
将数据结构和它的操作独⽴放到单独的源代码⽂件中,这时就需要设计合适的接⼝,以便于模块之间互相调⽤。
软件设计中的⼀些基本⽅法
KISS(Keep It Simple & Stupid)原则
⼀⾏代码只做⼀件事
⼀个块代码只做⼀件事
⼀个函数只做⼀件事
⼀个软件模块只做⼀件事
使⽤本地化外部接⼝来提⾼代码的适应能⼒
先写伪代码的代码结构更好⼀些
设计通常为程序提供了⼀个框架,程序员需要⽤⾃⼰的专业知识和创造性来编写代码实现设计。在从设计到编码的过程中加⼊伪代码阶段要好于直接将设计翻译成实现代码。
因为伪代码不需要考虑异常处理等⼀些编程细节,最⼤限度地保留了设计上的框架结构,使得设计上的逻辑结构在伪代码上体现出来。
从- 伪代码到实现代码的过程就是反复重构的过程,这样避免了顺序翻译转换所造成的结构性损失。因
此,先写伪代码的代码结构会更好⼀些。
另外我们也要有意识地⽤设计上的逻辑结构给代码提供⼀个编写框架,避免代码的⽆序⽣长,从⽽破坏设计上的逻辑结构。
消费者重⽤和⽣产者重⽤
Consumer Reuse:消费者重⽤是指软件开发者在项⽬中重⽤已有的⼀些软件模块代码,以加快项⽬⼯作进度。软件开发者在重⽤已有的软件模块代码时⼀般会重点考虑如下四个关键因素:
该软件模块是否能满⾜项⽬所要求的功能;
采⽤该软件模块代码是否⽐从头构建⼀个需要更少的⼯作量,包括构建软件模块和集成软件模块等相关的⼯作;
该软件模块是否有完善的⽂档说明;
该软件模块是否有完整的测试及修订记录;
Producer Reuse:我们清楚了消费者重⽤时考虑的因素,那么⽣产者在进⾏可重⽤软件设计时需要重
点考虑的因素也就清楚了,但是除此之外还有⼀些事项在进⾏可重⽤软件设计时牢记在⼼,我们简要列举如下:
通⽤的模块才有更多重⽤的机会;
给软件模块设计通⽤的接⼝,并对接⼝进⾏清晰完善的定义描述;
记录下发现的缺陷及修订缺陷的情况;
使⽤清晰⼀致的命名规则;
对⽤到的数据结构和算法要给出清晰的⽂档描述;
与外部的参数传递及错误处理部分要单独存放易于修改;
接⼝的基本概念
尽管已经做了初步的模块化设计,但是分离出来的数据结构和它的操作还有很多菜单业务上的痕迹,我们要求这⼀个软件模块只做⼀件事,也就是功能内聚,那就要让它做好链表数据结构和对链表的操作,不应该涉及菜单业务功能上的东西;同样我们希望这⼀个软件模块与其他软件模块之间松散耦合,就需要定义简洁、清晰、明确的接⼝。
这时进⼀步优化这个初步的模块化代码就需要设计合适的接⼝。定义接⼝看起来是个很专业的事情,其实在我们⽣活中⽆处不在,⽐如我们看的电视剧中“天王盖地虎,宝塔镇河妖”就是⿊社会接头定义的接⼝,⽐如两个⼈对话交流沟通使⽤的就是汉语普通话或标准英语这么⼀个接⼝规范。
接⼝就是互相联系的双⽅共同遵守的⼀种协议规范,在我们软件系统内部⼀般的接⼝⽅式是通过定义⼀组API函数来约定软件模块之间的沟通⽅式。换句话说,接⼝具体定义了软件模块对系统的其他部分提供了怎样的服务,以及系统的其他部分如何访问所提供的服务。
在⾯向过程的编程中,接⼝⼀般定义了数据结构及操作这些数据结构的函数;⽽在⾯向对象的编程中,接⼝是对象对外开放(public)的⼀组属性和⽅法的集合。函数或⽅法具体包括名称、参数和返回值等。
接⼝规格是软件系统的开发者正确使⽤⼀个软件模块需要知道的所有信息,那么这个软件模块的接⼝规格定义就必须清晰明确地说明正确使⽤本软件模块的信息。⼀般来说,接⼝规格包含五个基本要素:
接⼝的⽬的;
接⼝使⽤前所需要满⾜的条件,⼀般称为前置条件或假定条件;
使⽤接⼝的双⽅遵守的协议规范;
接⼝使⽤之后的效果,⼀般称为后置条件;
接⼝所隐含的质量属性。
可重⽤软件模块的接⼝设计范例
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
该接⼝的⽬标是从链表中取出链表的头节点,函数名GetLinkTableHead清晰明确地表明了接⼝的⽬标;
该接⼝的前置条件是链表必须存在使⽤该接⼝才有意义,也就是链表pLinkTable != NULL;
使⽤该接⼝的双⽅遵守的协议规范是通过数据结构tLinkTableNode和tLinkTable定义的;
使⽤该接⼝之后的效果是到了链表的头节点,这⾥是通过tLinkTableNode类型的指针作为返回值来作为后置条件,C语⾔中也可以使⽤指针类型的参数作为后置条件;
该接⼝没有特别要求接⼝的质量属性,如果搜索⼀个节点可能需要在可以接受的延时时间范围内完成
搜索;
#微服务的概念
由⼀系列独⽴的微服务共同组成软件系统的⼀种架构模式;
每个微服务单独部署,跑在⾃⼰的进程中,也就是说每个微服务可以有⼀个⾃⼰独⽴的运⾏环境和软件堆栈;
每个微服务为独⽴的业务功能开发,⼀般每个微服务应分解到最⼩可变产品(MVP),达到功能内聚的理想状态。微服务⼀般通过RESTful API接⼝⽅式进⾏封装;
系统中的各微服务是分布式管理的,各微服务之间⾮常强调隔离性,互相之间⽆耦合或者极为松散的耦合,系统通过前端应⽤或API⽹关来聚合各微服务完成整体系统的业务功能。
微服务架构的基本概念可以简单概括为通过模块化的思想垂直划分业务功能,传统单体集中式架构和微服务架构如下图⽰意
RESTful API
REST即REpresentational State Transfer的缩写,可以翻译为”表现层状态转化”。有表现层就有背后的信息实体,信息实体就是URI代表的资源,也可以是⼀种服务,状态转化就是通过HTTP协议⾥定义的四个表⽰操作⽅式的动词:GET、POST、PUT、DELETE,分别对应四种基本操作:
GET⽤来获取资源;
POST⽤来新建资源(也可以⽤于更新资源);
PUT⽤来更新资源;
DELETE⽤来删除资源。
接⼝与耦合度之间的关系
更细致地对耦合度进⼀步划分的话,耦合度依次递增可以分为⽆耦合、数据耦合、标记耦合、控制耦合、公共耦合和内容耦合。这些耦合度划分的依据就是接⼝的定义⽅式,我们接下来重点分析⼀下公共耦合、数据耦合和标记耦合。
公共耦合:当软件模块之间共享数据区或变量名的软件模块之间即是公共耦合,显然两个软件模块之间的接⼝定义不是通过显式的调⽤⽅式,⽽是隐式的共享了共享了数据区或变量名。
数据耦合:在软件模块之间仅通过显式的调⽤传递基本数据类型即为数据耦合。
标记耦合:在软件模块之间仅通过显式的调⽤传递复杂的数据结构(结构化数据)即为标记耦合,这时数据的结构成为调⽤双⽅软件模块隐含的规格约定,因此耦合度要⽐数据耦合⾼。但相⽐公共耦合没有经过显式的调⽤传递数据的⽅式耦合度要低。
怎样写代码 自己做编程通⽤接⼝定义的基本⽅法
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论