在程序开发的过程当中,相同的功能每每有不一样的实现方式。对于能够实现一样功能的不一样代码,复杂度是用于比较其质量优劣的重要指标。html
在本文中,代码复杂度是指代码被理解/修改的难易程度。越容易被理解、修改的代码的复杂度越低;反之其复杂度越高。程序员
复杂度低的代码比复杂度高的代码有更多好处,好比,面试
ABAP开发是在SAP系统中进行的,而SAP是企业的核心信息系统,其中会包含复杂的业务逻辑,一般由ABAP实现,并须要长期的维护。在这样的工做中,ABAP代码的复杂度对系统维护成本甚至项目的成败有着重要的影响。数据库
在下文中,我会介绍几种有助于最小化代码复杂度的通用思路,并尝试把它们和实际的ABAP开发工做结合起来,帮助理解。编程
做者水平有限,若是读者发现了任何问题,欢迎评论指出。app
本文连接:http://www.javashuo.com/article/p-vzshfkof-co.html编辑器
原创内容,转载请注明ide
两年前,我第一次参与的SAP项目刚完成不久。那时我对本身的技术至关自信,在项目中,我不只完成了多种类型的功能开发,并且也读完了整个项目的新开发代码。即便对于一些没作过的新的功能需求,我也每每能在不依赖乙方同事的状况下独立查找资料完成。自信满满的我决定换份新工做,并获得了一个面试机会。第一轮面试考察的是一些经常使用功能的实现和对业务流程的了解程度,如我预想的那样,本身顺利经过。在第二轮面试中,对方问到"对模块化的理解和实践",这是个出人意料的问题,我努力地思考了一番,殊不知道该说什么,因而被客客气气请了出去…模块化
经历了此次失败后的我,不断地思考着模块化设计。若是再次面对那个问题,也许我应该这样回答:函数式编程
模块是系统中独立的、可替代的单元。模块化设计,便是把系统分解为模块的集合。模块的形式多种多样,能够是form、method、function Module、class、或report等。在理想的世界中,每一个模块都彻底独立于其它模块:开发者在任何模块中工做的时候,都不须要知道有关其它模块的知识。在这种理想状态下,系统复杂度取决于系统中复杂度最高的模块。
固然,实践与理想不一样,系统模块间总会多少有些依赖。当一个模块变化时,其它模块可能也须要随之而改变。模块化设计的目标就是最小化模块间的依赖。
为了管理依赖,咱们能够把模块当作两部分:接口和实现。
接口包含了所有的在调用该模块时须要的信息。接口只描述模块作什么,但不会包含怎么作。
完成接口所作出的承诺的代码被称为实现。
接口中包含2种信息:正式的和非正式的。
正式的信息在代码中被显式指定,程序语言能够检查其中的部分正确性。好比,方法的签名就是正式的信息,它包含参数的名称和类型,返回值的类型,异常的信息。不少程序语言能够保证代码中对方法的调用提供了与方法定义相匹配的参数值。
接口里面也包含非正式的元素。非正式部分没法被程序语言理解或强制执行。接口的非正式部分包含一些高层行为,好比函数会根据某个参数的内容删除具备相应名字的文件。若是某个类的使用存在某种限制,好比方法的调用须要符合特定顺序,那这也属于接口的一部分。凡是开发者在使用模块时须要了解的信息,均可以算做模块接口的一部分。接口的非正式信息只能经过注释等方式描述,程序语言没法确保描述是完整而准确的。大部分接口的非正式信息都比正式信息要更多、更复杂。
正式的信息和非正式的信息都是复杂度的来源,清晰的接口定义有助于开发者了解在使用模块时须要知道的信息,从而避免一些问题。
以function module为例,在function module编辑器中看到的function module名,和前6个标签,加上function module文档(若是有),都属于它的接口。而source code中的代码,则属于实现。以下图
固然,若是该function包含任何隐性的使用信息,它也算作接口的一部分。好比,若是使用SAVE_TEXT保存长文本,一般要有一个COMMIT操做来提交修改。“须要使用COMMIT提交修改”,一样是SAVE_TEXT的接口的一部分。
进行模块化设计,是减少代码复杂度的第一步。由于,开发者在进行模块内部开发时,只须要关注 当前模块的接口+当前模块的实现+其它相关模块的接口。他只须要关注整个软件系统的一小部分,接触的东西变少,会使理解工做内容的速度大大增长,也会使犯错的机会变小。对于试图理解当前系统的部分功能而不作修改的人,这种设计一样会减轻人们的负担,由于人们一般只须要经过模块们的接口来了解程序的功能。
经验较浅的程序员容易犯的一个错误是,只考虑程序中的正常状况,即所谓的happy path,而没有(足够多地)考虑异常情形。不周全的考虑可让程序员快速完成功能,可是接下来则会致使测试中的频繁翻车,程序员不得再也不对程序进行种种的修补,致使代码总体的复杂度迅速升高。此外,即使是在一开始已经考虑到了各类异常情形,为了处理它们,也会给程序增长必定的复杂度。本节内容的主题是,如何合适地尽可能减少由异常情形引发的复杂度。下面介绍具体的三种办法,
第一种办法是从概念上消除异常。异常是相对正常而言的,功能的定义能够影响到异常的定义。ABAP SQL有插入语句,代码以下,
INSERT ztable FROM TABLE @lt_something.
可是,开发者一般不得不考虑主键重复引发的异常处理。因此在这一语句后面,还要检查sy-subrc返回值,根据判断作进一步处理...代码所以变得复杂。
如何避免此处的异常处理?假设咱们对功能的设定作出一些改变,从“把内表的数据插入数据库”改成“保证数据库中存在内表的数据”,在这个新功能的内部判断插入语句的执行状况,若是由于主键重复致使插入失败,则改成按主键更新数据库表,此时再也不须要在调用时进行相关异常处理。
说到这里读者已经知道,这个“新功能”就是ABAP中的关键字MODIFY,
MODIFY ztable FROM TABLE @lt_something.
使用MODIFY而不是INSERT的话,一种常见异常的定义便消失了,代码的总量和复杂度所以会减小。固然,前提是MODIFY的功能和需求相匹配。有些资深开发者由于惧怕新人不了解MODIFY的原理而禁止他们使用这个关键字,是因噎废食的作法。
把异常隐藏在较低层面是第二种作法,这种作法可使高层的代码在不须要了解异常存在的状况下进行工做,从而减小高层的复杂度。SAP系统中的一个例子是tRFC。
对于tRFC而言,远端系统不须要在RFC客户端程序运行tRFC的时候可用。tRFC组件将被调用的RFC函数和相关数据存储在SAP系统的数据库里,包含一个惟一的事务标识符(transaction identifier,TID)。若是调用发送了,接收系统倒是宕机状态,调用会保留在本地队列中一段时间。调用对话程序能够在不等待远程调用成功/失败的状况下继续运行。若是接收系统在一段时间后仍然不可用,调用将被计划为后台做业运行。
在tRFC的例子中,高层调用者不须要了解对方系统的状态,也不须要进行传输失败的处理,这一切都由低层完成了。高层程序的复杂度也会所以获得控制。
与其分散地处理程序中不一样部分产生的异常,不如把它们集中交给高层的程序,进行统一的处理。SAP系统中的一个例子是BAPI。绝大多数BAPI使用一个名为RETURN参数返回全部的错误,这样一来就能够由高层调用者对可能产生的错误进行统一的处理,而不是在产生错误的地方进行分散、个别的处理。
“Hi 氢氦,我在测试中遇到了这个错误消息,麻烦你看一下缘由。”
“什么?这和咱们的修改毫无关系,这个报错属于B功能,而你知道咱们改的只是程序的A功能,!”
“是的,可是我得保证此次修改没有影响到B功能,因此请你调试调查缘由。”
测试的一个难题是测试者不知道看似单纯的修改会带来什么样的复杂问题,因而只好求助于人工检查,开发者每每就是那个不幸的工人,使用纯函数能够帮助避免这类状况的发生。
最近流行的函数式编程十分强调纯函数的概念。纯函数是指符合如下条件的函数,
这要求函数内部不能存在“反作用”。
它的输出结果的肯定不该该依赖输入参数外的任何内容,例如,不能够由于本地测试环境中没有相应的数据库就产生“链接数据库异常”致使没法返回结果。
它也不该该改变除了返回结果之外的任何内容,例如,不能够改变全局可变状态。
知足以上条件的函数,能够被称为纯函数。
从模块化的角度来看,全局状态和对外部系统的链接都属于接口的一部分。纯函数不会与这些东西产生交互,所以它的接口会更简单,复杂度更低。
虽然ABAP不是函数式语言,但它依然能够有纯函数,而且开发者能够经过写纯函数而受益。
在上面的例子中,若是开发者能够证实A、B功能分属2个模块,并且它们都属于纯函数,那么只要证实A的变动不会改变B的输入,便可证实修改没有致使对方给出的错误。
系统中的模块能够分为接口和实现,换一个角度思考的话,代码也能够看做需求文档的实现。需求语言的准确性,对代码产物的质量有着直接的影响。在SAP开发中,业务逻辑的实现是首要目标,业务复杂度也每每是代码复杂度的最主要来源。
程序是需求的实现,所以代码应当尽可能包含需求文档中的信息。在需求文档的质量可靠的前提下,这样作能够有效提升程序的可读性,从而下降其复杂度。信息丢失的一个极端例子是代码混淆,显然混淆后的代码复杂度将大大升高。
固然,程序语言和系统内部的信息和需求文档中的天然语言是有差异的,这也是程序员存在的意义。在实际的开发中,能够尝试经过增长一个抽象层的方式来保留需求文档中的信息。好比,在高层对需求语言进行建模,在低层实现实际的执行过程。
注意:若是需求文档很长,代码实现不多的话,意味着工做可能存在某些问题,多是需求内容冗余、功能设计不合理、实现不完整、信息丢失等,此时要从新思考相关工做内容,以确保未出现这些问题。
保留信息的前提条件是明确信息,需求文档中应当有词汇表,帮助开发人员迅速捕捉和理解最重要的业务语言,以便把它们落实在程序中。
对于在中文环境下的工做而言,这点尤为重要。ABAP并不适合使用中文命名,而普通的程序员没有能力把中文的业务语言转换成英文的,须要由业务顾问来完成这项意义重大的工做。
最后,还有一种显而易见的办法是将某些业务逻辑转移到ABAP以外实现,好比下推到数据库层面、使用配置工具实现(BRF+)、交给中间件等。
优势:从ABAP角度来看,这种办法最有效地避免了复杂度的增长。
缺点:从全局来看,复杂度并无真正消失,只是随着工做量转移到了另外一个地方。
要从系统的总体复杂度的角度来考虑实现逻辑的位置,好比,人们经常使用配置表配合ABAP代码来实现自定义逻辑,BRF+中的decision table能够实现类似的功能,若是需求但愿获得用户在实际使用程序时对不一样逻辑的命中率的话,那么decision table可能会更合适一些,由于BRF+中包含跟踪模式,能够被直接利用,这样就能够经过写简单的代码来获得结果,避免引入更多的复杂度。
长期关注本博客的读者可能会注意到,这个博客已经有段时间没有更新ABAP相关内容,这是由于从今年开始,个人工做重心已经逐渐转移到Spark和Dynamics等其它方面的开发,花在SAP上面的时间变得不多。刚好最近参加了一个新的SAP项目,它唤醒了我对ABAP的一些记忆,因而趁热打铁,写了这篇文章。这篇文章算是在ABAP角度上将最近学习到的东西进行的一个总结复习。但愿它能对读者有所帮助,也真诚地但愿能收到反馈,共同讨论相关话题。