规范开发行为

建议154:不要过度设计,在敏捷中体会重构的乐趣

在以下几个情形, 我们不得不更改软件的设计:

  • 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个人意志有所变更。
  • 如果竞争对手增加了新需求,我们也不得不为正在研发的新产品调整设计方案。
  • 刚开始的架构太糟糕了,这可能源于设计经验的不足或者架构师的不负责任。

以上描述了内部和外部的必须改需求和设计的几种场景,也就是说,在软件开发的过程中,变化几乎总会发生;

为了捕捉需求的不断变化,软件开发必须变得足够敏捷,设计也需要停留在高层次上,具有指导作用。而功能模块(代码)则需要逐步改进,我们把改进的过程叫做重构;

传统的瀑布开发不能够满足需求的变化,在软件领域被各类敏捷开发框架所替代;

敏捷开发模式(Agile)把整个开发过程分成了一个一个的迭代,每个迭代的周期大概是两周到一个月。每个迭代大致分为如下几个步骤。

​ 敏捷开发使用“用户故事”(User Story,在TFS敏捷规范中,它被归类到BackLog)来核定需求和工作量指标。在一个迭代开始的时候,开发团队应该挑选用户故事作为本次迭代的目标;而在每一次迭代结束的时候,用户故事应该开发完毕。整个开发部门必须发布一个可以运行的版本交付给客户(对象可以是真实的用户,也可以是团队运营部门)。这有助于客户及时调整他们对产品的预期,并修正可能存在的需求变动。而作为开发团队,也可以根据客户的反馈修正需求,甚至是设计。

​ 正是因为敏捷开发拥抱变化,所以站在整个开发流程来看,设计应该是可以修改的,而落实到代码上来,也就是重构的地位提升了。在敏捷开发体系中,我们可以将其作为一个任务(Task)引入整个开发流程中。

​ 作为一个团队,需要定期审视模块是否可以被重构。而作为开发人员,建议一旦嗅到代码的坏味道(Bad Smell),就需要重构我们的代码。

​ 不追求让代码第一个版本就保持非常整洁的程度,那不现实,而且会让开发者觉得无从下手。当然,我们更不应该让繁杂的代码永远保持在第一个版本的状态,那样的代码,让我自己都不满意。

建议155: 随着生产代码一起提交单元测试

​ 首先有一个问题:我们害怕修改代码吗?在面对乱糟糟代码时候,很多时候都是决定去重构代码,但是重构是有风险的,可能在一段时间后收到测试版的报告:新的版本的产品,没有之前的稳定,性能没有以前的更好了,引入的Bug也更多了。(重构火葬场, 这一点在公司深有体会,也确实遭遇过对应的问题,有了相应的困扰)。看上去,重构的代码看上去质量更高了,可实际测试结果却不如人意。

​ 相信很多程序员都因为此类问题纠结过。我们要修改的代码也许来自某些不负责任或经验欠佳的程序员,也许这些代码是自己一年前写的,但是看上去已经惨不忍睹。我们想要修改这些代码,却担心重构出别的问题。即便是一个开发周期中的产品,也会有这样的选择出现。某个模块可能已经提交测试并确认通过,不过现在发现有更优的算法和逻辑,改还是不改,成了一个问题。

​ “单元测试”减轻甚至消除了开发者这种恐惧。如果项目没有测试代码,说明我们只是生产“定时炸弹”。很多人将生产代码和测试代码分别对待,这是一种过时的做法。程序员在提交自己的生产代码时,必须同时提交自己的单元测试代码。很多现代化的版本管理工具可以在后台订制项目构建计划,自动运行测试项目,统计代码覆盖率,并生成相应报告。(大佬们应该在早上一边喝咖啡,一边读取这样的报告)。

​ 有了测试代码做保证,在很大程度上我们可以放心去重构了。如果某个功能偏离了既有成果,就会有醒目的提醒。

​ 将单元测试放在首要地位的一种开发模式是TDD(Test Driven Development测试驱动开发)模式, TDD有三条严格的定律:

  • 在编写不能通过测试的单元测试前,不要编写任何生产代码;
  • 只编写恰好无法通过的单元测试,不能编译也算不通过;
  • 只编写刚好足以通过当前失败测试的生产代码;

实际上,大部分团队都不会严格遵循TDD的定律;但即使我们的团队没有完全采用TDD的开发模式,也可以借鉴这些定律来编写我们自己的测试代码。我们无需一次性编写完全部的测试代码,那没有必要,这跟过度设计一样,也不可能实现。事实上,我们应该逐步地编写测试代码,而且按如下步骤来编写:

测试代码->生产代码->测试代码

下面展示一个单元测试的例子:

1
2
3
4
5
6
7
public class SampleClass
{
public int Add(int a, int b)
    {
    	return a + b;
    }
}

鼠标移到被测试方法上,使用VS右键创建单元测试,VS会为我们创建一个测试方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace SampleClass.Tests
{
    [TestClass()]
    public class ProgramTests
    {
        // Add Test
        [TestMethod()]
        public void AddTest()
        {
            SampleClass target = new SampleClass();
            int a = 1;
            int b = 2;
            int expected = 3;
            int actual;

            actual = target.Add(a, b);
            Assert.AreEqual(expected, actual);
            // Assert.Inconclusive("Verify the correctness of Add");
        }
    }
}

​ 作者在这里吐槽了一下VS这个可视化测试工具太重了,导致开发的过程中运行测试代码太繁琐也太耗时。可以考虑用测试工具TestDriven.NET。(这个没试过, 想试的去找下资料就好)

单元测试应该注意的几个点:

  • 首先,单元测试不应引入任何人机交互的内容。如,测试过程中不应该弹出对话框,等待用户输入或确认。单元测试不应该是被阻滞的。
  • 其次,多线程也不属于单元测试范畴,单元测试应该是快速被执行的,而不是需要等待的。
  • 最后,单元测试不应该跨应用程序域,例如,数据访问或者远程通信属于集成测试范畴,而不是单元测试。

建议156:利用特性为应用程序提供多个版本

基于以下理由,我们需要为应用程序提供多个版本:

  • 应用程序有体验版和完整功能版;
  • 应用程序在迭代过程中需要屏蔽一些不成熟的功能;

假设我们的应用程序共有两类功能:第一类功能属于单机版,而第二类的完整版还提供了在线功能。那么,在功能上,需要定制两个属性“ONLINE”和“OFFLINE”。在体验版中,我们只开放“OFFLINE”功能。要实现此目的,不应该提供两套应用程序,而应该通过最小设置。为一个应用程序输出两个发布版本。这一切,可以通过.NET中的特性(Attribute)来实现。

要实现两个不同的功能,需要在程序入口这个文件最开头定义:

#define ONLINE

//#define OFFLINE

这条编译符号一定要在文件的最开头。同时,该定义只对本文件有效。如果要想定义全局编译符号,则必须在项目属性中定义。如下图:

1638024244282

如果想定义多个全局宏定义,则用逗号隔开,如“OFFLINE,ONLINE”。

如果要发布所有功能,就输入条件编译符号#define ONLINE,如果要发布离线版功能,就用#define OFFLINE。运行如下。

建议157:从写第一个界面开始,就进行自动化测试

如果说单元测试是白盒测试,那么自动化测试就是黑盒测试。黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的。具体的自动化操作可以学习Code UI Automation.

1.黑盒测试

​ 黑盒测试把产品软件当成是一个黑箱子,只有出口和入口,测试过程中只要知道往黑盒中输入什么东西,知道黑盒会出来什么结果就可以了,不需要了解黑箱子里面是如果做的。   即测试人员不用费神去理解软件里面的具体构成和原理,只要像用户一样看待产品就可以了。

2.白盒测试

​ 白盒测试是一种以理解软件内部结构和程序运行方式为基础的软件测试技术。通常需要跟踪一个输入在程序中经过了哪些函数的处理,这些处理方式是否正确。

3.手工测试和自动化测试

​ 有些人认为做手工测试是很简单的一件事,只需要点点点;而做自动化很难的事,很多人都避之不及。   其实,这两种方式都是需要结合使用。自动化不可以代替手工,手工测试具有不可替代性。   自动化测试是对手工测试的一种补充,主要应用在回归测试。主要集中在稳定的功能上面,帮助手工测试验证稳定模块的功能。   对于数据的正确性,界面美观,业务逻辑等的满足程度,自动化测试是做不了的,都需要由手工来做。毕竟机器是死的,人们对是非的判断,逻辑推理能力是工具不具备的。   自动化优势是借助计算机的计算能力,可以重复地、不知疲惫的进行,对于数据能进行精确的,大批量的比较,而且不会出错。   综上我们可以知道,手工测试和自动化测试一个都不能少,而且需要有机结合,充分利用优势,测试人员提供各种方法和手段。

4.单元测试

​ 单元测试,即针对的是软件设计中的最小单位–程序模块进行正确性检验的测试工作。其目的在于发现每个程序模块内部可能存在的差错。

​ 单元测试应该由谁来做?通常分为两派:

​ 一派认为应该由开发来做,因为代码是开发人员编写出来的,开发人员应该对自己所编写的代码负责,而且开发人员对于自己的代码熟悉程度最高,所以编写起来较测试人员容易,更可以得到优质的代码。 ​ 另一派则认为应该由测试人员来做,因为开发人员不像测试人员有敏锐的测试思维,所以测试的没有测试人员精细,所以需要测试人员来做单元测试。 ​ 事实上,这个问题都各持己见,但是最好的办法可能是达成双方合作;即由测试人员设计测试用例,开发人员编写测试代码,测试人员执行测试用例;