翻译 | 如何成为一名合格的敏捷开发者

2019-08-171679

原文自博客发布平台medium,作者为 better-programming 的 Ravi Shankar Rajan,传送门

如果你想知道什么叫做敏捷开发者,首先你得先了解什么叫做敏捷开发。

敏捷开发是一个用于表述迭代和增量软件开发方法论的术语,例如:

  • scrum,当今最流行的敏捷框架,用于产品开发管理
  • kanban,能够帮助开发者在开发任务中做好规划的一种方法论
  • 极限开发,为开发者提供一系列用于完成特定项目开发的规则的一种方法论

并且,一个敏捷开发者并不是指你们所认为的那些把自己绑在独立的空间中,戴着耳机享受音乐,与世隔绝的开发者。敏捷开发中开发者的角色明显要显得更为宽广和多元。

敏捷开发者更关注与可持续开发。可持续性意味着良好的评估、有效的代码分支管理策略、保证质量的自动化测试以及持续部署用于从用户中获得快速的反馈。

这一切意味着整个组织都应该接受,质量比 deadline 和日程规划都要重要得多,这个观念。

也就是说,敏捷开发的基石是将区域转换为动态变量(像每一个变更的商业/市场需求),以此团队能够保证质量、构建一个活力四射的开发文化以及保持业务的紧密性。

还有,作为一个工作于敏捷环境中的开发者来说,重要的是他们需要了解组织中遵循的所有敏捷开发方法论、工具以及指南。

除此之外,敏捷开发者应该具有良好的沟通技巧用于团队合作以及与客户的交谈。

最重要的是,敏捷开发者应该了解组织中整个开发过程的实现以及流程。

这里并没有捷径,但以下所列出的实践点可以帮助你更好地融入到敏捷程序开发、测试和 debug 中。

意图导向编程

意图导向编程适用于学习任意编程语言的条件中。

当我们学习任一种语言的时候,会很自然地倾向于将问题分解为一系列功能性步骤上。步骤细化得越高,你就能够更能了解语言深层。这种技术正是证实了这一点。

意图导向编程是说,比起按部就班地编写代码,想象你自身已经有一套合适的方法并将其实践到当前情况中会显得更能解决问题。

问问你自己:"这样一个理想的方法需要哪些输入输出呢?还有,在现在对我来说,怎么取名才令我自己印象深刻来让我自己觉得这个方法已经存在?"

并且,由于方法实际上并不存在,所以除了你自己的想象外,其他因素并不会影响到你。

思考下下面的代码:

REPORT ysdnblog_class_ideal.
parameters : p_rows type count default ‘100’.
* — — — — — — — — — — — — — — — — — — — — — — — -*
* CLASS lcl_main DEFINITION
* — — — — — — — — — — — — — — — — — — — — — — — -*
CLASS lcl_main DEFINITION.
PUBLIC SECTION.
CLASS-METHODS : start.
PRIVATE SECTION.
METHODS : get_data ,
display.
CLASS-DATA : lr_main TYPE REF TO lcl_main.
DATA it_mara TYPE mara_tt.
ENDCLASS. “lcl_main DEFINITION
* — — — — — — — — — — — — — — — — — — — — — — — -*
* CLASS lcl_main IMPLEMENTATION
* — — — — — — — — — — — — — — — — — — — — — — — -*
CLASS lcl_main IMPLEMENTATION.
METHOD start.
CREATE OBJECT lr_main.
lr_main->get_data( ).
lr_main->display( ).
ENDMETHOD. “start
METHOD get_data.
 — — WRITE SOME CODE HERE……
ENDMETHOD. “GET_DATA
METHOD display.
 — — WRITE SOME CODE HERE……
ENDMETHOD. “display
ENDCLASS. “lcl_main IMPLEMENTATION
START-OF-SELECTION.
lcl_main=>start( ).

start() 方法是执行报告时调用的当前类的公共方法。

其他的方法,get_data()display(),是类的私密方法,它们只会被类对象在内部调用。我们可以将对这些方法的简单调用称为功能性步骤。

关键是它们是作为类内部实现的一部分,而不是用于外部而存在。

当然它们还没真正地实现。所以当你尝试编译这部分代码,编译器会抛出错误来说明它们并不存在,它们必须被编写为能够被编译的代码才行。

这就是下一步我们需要做的事情。

在意图导向编程中,我们允许我们自己将注意力放到如何将问题分解到需求的整个大环境中。

我们并不会编写和执行那种大块冗余的代码。我们所要做的是将代码分成一步一步的功能块并且一步一步得实现它们。

思考下下面我所实现的功能步骤 get_data()display()

METHOD get_data.
SELECT * FROM mara INTO TABLE me->it_mara UP TO P_rows ROWS .
ENDMETHOD. “GET_DATA
METHOD display.
DATA : lr_table TYPE REF TO cl_salv_table.
cl_salv_table=>factory( IMPORTING r_salv_table = lr_table
CHANGING t_table = me->it_mara ) .
lr_table->display( ).
ENDMETHOD. “display

在这整个过程中,没有过多的变化。我们只是以不同的方法和不同的规则去简单地编写这些代码而已。

测试驱动开发(TDD)

当你第一次使用 TDD 的时候也许你会觉得很奇怪。

为什么会这样呢?先编写单元测试?谁会做这种傻事?

话虽如此,但它可是敏捷开发中最重要的概念之一。

让我们举一个简单的例子来说明下吧。

这个例子是假设我们有一个带有方法 SUM 的类 LCL_SUM。这个方法的作用是对数字进行增加操作。

它将一个数字作为最重要的参数并且与其对象自身进行一个加法操作从而得到结果,现在让我们把这个方法称作生产方法吧。

这个类的代码如下所示:

CLASS lcl_sum DEFINITION.
PUBLIC SECTION.
METHODS: SUM IMPORTING iv_1 TYPE i
RETURNING VALUE(rv_sum) TYPE i.
ENDCLASS. “lcl_sum DEFINITION
*
START-OF-SELECTION.
* Nothing here yet
*
*
CLASS lcl_sum IMPLEMENTATION.
METHOD SUM.
rv_sum = iv_1 * iv_1. "我故意使用乘法而非加法来产生错误" sum
ENDCLASS. “lcl_sum IMPLEMENTATION.

编写测试类

现在我们编写一个测试类。

在 SAP 当中,当你定义这中类的时候需要添加关键字 FOR TESTING。这样可以将此类从生产代码( production code)中分离出来。

CLASS lcl_test DEFINITION FOR TESTING
PUBLIC SECTION.
METHODS: m_sum FOR TESTING.
ENDCLASS. “lcl_test DEFINITION
*
CLASS lcl_test IMPLEMENTATION.
METHOD m_sum.
ENDMETHOD. “m_sum
ENDCLASS. “lcl_test IMPLEMENTATION

测试方法实现

在这个测试方法中,你需要做的是测试这个生产代码。

所以,为了测试 LCL_SUMSUM 方法,你需要实例化 LCL_SUM 的引用对象,并且调用方法 SUM 和传递虚拟数据。

基于这些虚拟数据,方法会返回方法的实际结果。

基于虚拟数据,你会知道期望值是什么。落日入,如果你将 3 作为参数传入到 SUM 方法中,它会返回 6,因为增加了 3。

当从测试的生产代码或者方法中获得实际结果后,你需要对结果进行比较。

如果实际值和期望值不相同,你需要让系统知道这当中出现了问题并且显示一些适当的信息。

CLASS lcl_test IMPLEMENTATION.
METHOD m_sum.
DATA: o_cut TYPE REF TO lcl_sum.
DATA: lv_result TYPE i.
*
CREATE OBJECT o_cut.
lv_result = o_cut->sum( 3 ).
*
cl_aunit_assert=>assert_equals(
EXP = 6
act = lv_result
msg = ‘something wrong in output’
).
ENDMETHOD. “m_sum
ENDCLASS. “lcl_test IMPLEMENTATION

单元测试结果

这提醒我在生产方法中的实现出现一些错误。

对的,如果你仔细观察 SUM 的方法实现,我有一个错误。我是用了乘法而非加法。所以我需要更正它并且重新运行测试来验证是否成功。

我们时常在尝试编译或者运行代码前花费大量的时间在编写代码上。但是,代码的执行和测试在这里每次只需要花费2分钟左右。

因此,简而言之,TDD 是遵循,首先编写单元测试,再编写具体代码,再重构,终而复始,这个原则来进行实现短开发周期开发的。

单元测试是一种自动化测试,用于检查功能是否预期所望工作。你的第一个单元测试用例应该是失败的因为这是在你编写任何代码之前所写出来的。

在这做法中,你往测试用例完善一点,在生产代码中完善一点。两种代码共同增长,相互维护。测试用例适用于生产代码就像抗体和抗原一样。

将一切封装

当中的经验法则是:按策封装,按需调用(encapsulate by policy, reveal by need)。

当有疑惑的时候,隐藏它;当有需要的时候调用它们。

当抽象问题解决后,将其隐藏和封装。

抽象意味着:"你被允许以一个更高的细节点去观察所有对象",而封装则更进一步,它意味着:"你不允许通过任何级别的细节点来观察所有对象"。

这就是,继承,这个概念的由来了。

当在设计任何软件系统的时候,我们时常会寻找那些十分相像的对象。

例如,当在设计一个合约系统时,我们可以找到许多不同种类的合约(全职、临时、资产、刺激性合同等等),但是每种合约的一般属性是一样的。

因此,你可以穿件一个名为 contract 的通用对象,然后定义 fixed price (全职)合约作为继承 contract 对象的类并带有额外的属性,以此类推。

继承简化了设计因为你通过编写一个通用的对象来处理所有事情并且编写特定的对象来处理特定的场景。

不仅代码变得"洁净"了,也为使用该类来进行继承的开发者提供了一个极好的方法来分离不必要的细节。

继承是面向对象编程中其中一个重要的思想。它是一把双刃剑,当使用正确时可以提供极大的帮助;当错误地使用时会带来巨大的伤害。

简单地说,我们应该尽力将能够封装的事物进行封装,因为它能简化我们的维护过程。

面向接口设计(IOD)

通常,接口是连接另一个实体 — 一个方法,一个类,一个模块或者一个程序,的一种方式。

常见的接口形式是:

  • 对象的可访问方法集。
  • 协议,譬如文件传输协议(FTP)或者简单邮件传输协议(SMTP)。
  • 程序化接口,譬如 web service。

并且,面向接口设计将定义层从实现层中分离开来。关键在于确保这种简化的设计能够使得测试变得更简易以及提高可维护性。

接口仅包含一组方法的签名(名字、参数、返回类型以及调用后的期望值)。接口不能包含数据成员(属性),因为这意味着实现的一步。

定义和实现的分离在设计上有明显的优势。它允许在测试以及外部的表现(例如外部格式)中轻松完成替换实现并且可以明显地与内部实现分离。

同样,你可以将程序化内部接口从外部供应商(第三方接口)或者开发组提供的 API 中进行分离。开发自己所需的接口并且对其进行调整。

这样就可以随时创建干净、易调整和可读的代码了。

原型

敏捷以迭代的模式工作并且迭代要求工作原型需要定期进行展示和讨论。

也许原型的最大优点是能够表现设计的视觉反馈;一张图,即使规模再小,也比千字来得有用。图片不仅帮助我们确认设计的可行性,更有助于获得必要的"认同"和批准来继续推进。

原型也能作为一次性模型,用于在设计和编码之前理解项目的需求。本质上来说,原型是项目的试金石。

即使你已经对设计作出了无数次的修改,在软件开发的过程中,还是会有许多预想不到的问题会发生。

原型测试至少可以让开发团队知道问题的所在并且他们能够在软件发布前有机会去跟进。

与原则(discipline)一起使用,原型会成为开发者的主力工具,并将其用于代码和设计的冲突中。

可以参考 iPhone,这个回报可以说是非常巨大。

最后的话

敏捷开发不仅仅是一系列仪式。它是一种文化以及技术哲学。

它通过实践提高个人能力,借此在产品中构建一个坚固的技术基础并且在团队中产生合作文化。

敏捷开发的一个核心原则是稳步前进,这反过来,也可以让你能够稳步地交付产品,而不是零星地工作最后什么都产出不了。

开发者在敏捷团队中会更加投入,编写更好的代码和获得更多的乐趣。他们往往编写可维护的代码,构建良好的关系并且能够对项目最后的回报有一个明确的分配。

真正意义上的团队合作是越做越好、不断改进。

就像爱迪生说的那样:

There is a way to do it better — Find it.

分享
点赞3
打赏
上一篇:Docker常用命令笔记(一)
下一篇:翻译 | 什么是 面向对象 Matrix