转载说明

本文转载自本人的合作伙伴@mahoshojoHCG。尽管代码的大部分是我写的,但是后续的修改,以及本文的撰写由他完成。以下为原文内容。

前言

依稀记得,很久以前做过一个课表软件咕咕咕。在寒假的时候,做了一些微笑的工作,将前端与后端的代码分离到两个仓库里,然后@FerdinandSukhoi(下称SDL)将库重新搞了一下,形成了新的版本,还在其基础上开发出了命令行客户端。

在学习了软件构造的课程之后,感觉很多地方写的问题很大,因此这次在原来的基础上进行小规模的重构,使其符合软件工程的规范。

三大改造

暴露安全性修正

由于C#是一种超级语言,因此其有属性,一般不会有暴露私有字段,一般来说是没有这方面的风险的。但是我在做Code Review的时候发现了:

1
public List<ScheduleEntry> Entries { get; } = new List<ScheduleEntry>();

外界可以对这个List进行操作,可能会对ADT的内部造成毁灭性的破坏,因此这里要进行修改,修改后为:

1
2
private readonly List<ScheduleEntry> _entries = new List<ScheduleEntry>();
public List<ScheduleEntry> Entries => new List<ScheduleEntry>(_entries);

这样就保证外部在获取条目的时候得到的是List的副本,不会无意间将ADT进行修改。

但是,这样原来客户端中会进行的一些操作,比如增删条目就无法进行,因此还要提供两个方法来进行增删条目:

1
2
3
4
5
6
7
8
9
public void AddScheduleEntry(ScheduleEntry scheduleEntry)
{
_entries.Add(scheduleEntry);
}

public void RemoveAt(int index)
{
_entries.RemoveAt(index);
}

为了能够快速访问元素,还添加了this[]属性:

1
public ScheduleEntry this[int index] => _entries[index];

这样暴露安全性的修改完毕,而且客户端原有的逻辑得到保留,也没有损失性能。

还注意到,在代码中,有一些方法应该设置为peivate,有一些属性应该设置为只读,这些地方也进行了修改。

加注释

原来在编写这个类库的时候,对于方法没有XML注释,因此在调用方法的时候可能会出现不知道这个方法是做什么用的,因此这里给所有的方法和属性都加了XML注释。

这里举一个例子:

之前:

1
2
3
4
5
public Schedule(int year, Semester semester)
{
Year = year;
Semester = semester;
}

之后:

1
2
3
4
5
6
7
8
9
10
/// <summary>
/// 指定年份和学期创建空的课表
/// </summary>
/// <param name="year">要创建课表的年份</param>
/// <param name="semester">要创建课表的学期</param>
public Schedule(int year, Semester semester)
{
Year = year;
Semester = semester;
}

修改构造函数

Schedule类中,有一个构造函数的原型是:

1
public Schedule(Stream xlsStream);

这里接收的参数是一个Stream,有可能抛出异常,因此在这里将其转化为静态方法,转化后的方法签名为:

1
public static Schedule LoadFromStream(Stream inputStream);

这样代码更加安全。

写测试

是的,原来这个项目根本就没有测试,因此这里要写测试,为所有的公共方法与属性添加测试并且补充测试策略。

比如针对转化,添加了如下的测试:

1
2
3
4
5
6
7
8
9
10
11
12
[TestMethod]
public void TestWeekExpressionParse()
{
Assert.AreEqual("张三", entry.Teacher);
Assert.AreEqual("格物201", entry.Location);
Assert.AreEqual(15, entry.MaxWeek);
Assert.AreEqual((uint)0b11111111 << 8, entry.Week);
entry.ChangeWeek("1-3,10-13");
Assert.AreEqual(((uint)0b1111 << 10) + 0b1110, entry.Week);
entry = new ScheduleEntry(DayOfWeek.Monday, CourseTime.C12, "测试用课", "张三[7,11]单周格物201");
Assert.AreEqual((uint)0b10001 << 7, entry.Week);
}

而且,在测试的时候,发现了一个关于的编码的小bug,也进行了修复,说明了测试能够帮助我们发现代码中的内容,十分重要。

打包Nuget

在完成重构后,设置了CI脚本,在每次push后自动编译,制作nuget包,并且自动发布到.nuget.org中,实现了自动化部署。

尾声

通过对于软件构造这门课的学习,知道了软件工程的基本概念,对于自己目前还在维护的项目有很大的帮助。以后开启新的项目以及维护现有的项目的时候,注意到各种情况,并且积极写注释、写测试。这次重构这个库,收获真的很大。

EOF