在使用小型且经常提交的持续集成范例时,设计代码的最佳做法是什么?例如,鉴于一个很大的特点,如何将其分解为较小的部分?如何在旧的时候开发出改进的功能。等
例如,可以使用“功能切换”,以便新功能在代码中处于休眠状态,直到准备就绪。
我感兴趣的领域语言是Java / C#和类似的。
更新:注意,我不是在询问如何启用CI。这样的答案妨碍了建立构建服务器,自动化测试等的需要。问题是如何设计代码以便频繁,小型,提交工作。例如,所有测试都通过。在引入新功能以及修改功能时都有意义。
答案 0 :(得分:2)
首先,您要尝试遵守Open/Closed Principle
来自维基百科:
“这个想法是,一旦完成,只能修改类的实现以纠正错误;新的或更改的特性将要求创建不同的类。该类可以通过继承重用原始类中的编码。派生子类可能有也可能没有与原始类相同的接口。“
应用此技术,您最终会同时获得代码库中现有的两个版本的功能。当您实现切换时,需要在运行时在两个版本的代码之间切换,可能使用某种配置值和依赖注入。
对于需要修改底层数据模式的代码更改,您可以设置一种“连续转换”,它可以在运行时在两个实现之间动态转换数据。来自ebay的Randy Shoup在解释这个方面做得比我在InfoQ上的Evolvable Systems谈话中做得更好。
这些技术应该允许您对代码库进行较小的更改并更频繁地集成。
我认为乔希提出的所有观点也有助于实现这一目标。无论采用何种技术降低更换系统的风险,都可以让您以更快的速度工作。
答案 1 :(得分:1)
这将引发许多争论,但持续整合和TDD齐头并进。你需要的不仅仅是“编译吗?”测试级别,以确保不断变化的代码是健壮的。
如果您遵循高度的测试覆盖率,那么您推送的任何功能都将在构建中进行全面测试,然后才能被接受。这意味着您必须以一种允许您快速获得整个功能片段的方式进行编码,然后构建而不是向上构建。 (垂直切片)。
这在代码方面意味着什么?
您需要严重依赖Dependency Inversion Principle
而不是:
public class PersonService
{
private SqlDataAccess DataAccess = new SqlDataAccess();
public void SavePerson(Person person)
{
DataAccess.SavePerson(person);
}
}
你应该这样做:
public class PersonService
{
private IDataAccess DataAccess {get; set;}
public PersonService(IDataAccess dataAccess)
{
if(dataAccess == null)
throw new ArgumentNullException("dataAccess");
DataAccess = dataAccess;
}
public void SavePerson(Person person)
{
DataAccess.SavePerson(person);
}
}
这是一个简单的示例,但是您可以在充实UI和主要功能的同时,实现内存数据访问层等决策。仍然可以编写测试,因为您可以模拟服务,并且可以更快地推送功能,因为不是每个部分都必须100%存在。随着要求开始巩固,您可以对数据访问做出更明智的决策,只需将临时组件替换为更永久的组件,而不必担心破坏现有代码。
当然,将运行自动集成测试,以确保您不会破坏其余的代码库;)