改变我编写代码方式

科技   2024-12-20 05:52   上海  


在过去的时间里,我一直在使用C#进行开发工作,并且已经准备好迎接下一个挑战了。当时我面对两位资深开发人员,其中一位是西门子的首席架构师。

面试问题一开始都是最常规的那种,比如在C#方面的经验、对.NET框架的熟悉程度,或是应对特定编码挑战的方法。

我自信满满地作答,毕竟我花了数年时间开发应用程序、构建解决方案以及解决复杂问题。至少,我当时认为自己是这样做的。

意外转折

在问了几个简单的问题之后,首席架构师给我出了一个编码挑战,这个挑战乍一看挺简单的。

开发一个基础程序,用于从CSV文件中读取数据,按照类别进行筛选,然后以一种清晰、结构化的格式输出结果。

不算太复杂,我之前都写过上百个类似的脚本了。

于是我直接上手,迅速开始用自己最熟悉的C#特性来写代码。用一个简单的StreamReader来读取文件,用List<Product>来存储产品数据,然后通过循环按照类别对它们进行筛选和展示。

以下是我当时写的代码的简化版本:

public class Product
{
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}

public void ProcessProducts(string filePath, string categoryFilter)
{
var products = new List<Product>();

using (var reader = new StreamReader(filePath))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');

var product = new Product
{
Name = values[0],
Category = values[1],
Price = decimal.Parse(values[2])
};
products.Add(product);
}
}

var filteredProducts = products.Where(p => p.Category == categoryFilter);

foreach (var product in filteredProducts)
{
Console.WriteLine($"{product.Name}, {product.Price:C}");
}
}

代码能运行,而且我觉得效率还挺高的。

然后他抛给我一个重磅问题:

“这是编写这段代码的最佳方式吗?”

我愣住了。这代码能运行啊,而且也满足要求了呀,还能怎样呢?

就在这时,事情变得有意思起来了。

他开始跟我说,虽然代码是正确的,但可读性很差。解决方案必须具备可维护性、可扩展性,并且要清晰明了,这样其他开发人员才能看得懂。

这时我才意识到,我之前写代码几乎完全是面向机器的,而没有考虑到最终要维护我代码的那些人。

看看我上面那个ProcessProducts方法,乍一看好像还行,但如果不逐行查看的话,不一定能明白它到底在做什么。

架构师建议为了提高可读性和可维护性对代码进行重构。

第一步,我们把职责进行了分离——将解析、筛选和输出分别放到不同的函数中。

我是这样做的:

public IEnumerable<Product> LoadProducts(string filePath)
{
var products = new List<Product>();

using (var reader = new StreamReader(filePath))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');

var product = new Product
{
Name = values[0],
Category = values[1],
Price = decimal.Parse(values[2])
};
products.Add(product);
}
}

return products;
}

public IEnumerable<Product> FilterProductsByCategory(IEnumerable<Product> products, string category)
{
return products.Where(p => p.Category == category);
}

public void DisplayProducts(IEnumerable<Product> products)
{
foreach (var product in products)
{
Console.WriteLine($"{product.Name}, {product.Price:C}");
}
}

这样代码就更易于阅读和维护了。

现在,如果有人想要更改产品的筛选方式或者输出方式,他们只需要修改相应的部分就行,不用在整个方法里到处查找了。

考虑异常情况

接着,这位资深架构师又提出了一些问题,让我开始思考自己的编码方式以及如何应对那些预料之外的情况。

“要是CSV文件格式有误怎么办?”他问道,“要是某个产品有缺失字段或者价格无效怎么办?”

他说得对。我之前编码的时候都是基于一种假设——假设一切都会正常,代码会在所谓的“正常流程”下运行。

但实际上,数据往往是杂乱的。正如他所解释的那样,防御性编码就是要预见到可能出现的故障并为之做好准备。就算是那些很少发生的边界情况,你也得有所规划。

基于这个经验教训,我们添加了错误处理来应对文件格式或解析方面的意外问题:

public IEnumerable<Product> LoadProducts(string filePath)
{
var products = new List<Product>();

using (var reader = new StreamReader(filePath))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');

try
{
var product = new Product
{
Name = values[0],
Category = values[1],
Price = decimal.Parse(values[2])
};
products.Add(product);
}
catch (FormatException ex)
{
Console.WriteLine($"Error parsing product data: {ex.Message}");
}
}
}

return products;
}

好的代码不仅要有功能,还要具备健壮性。

设计模式

最后,他们要求我用策略模式(Strategy Pattern)来实现同样的代码,策略模式允许在运行时确定行为。我第一次用到它的时候就是在产品筛选这块。

以下是我们实现产品筛选的策略模式的方式:

public interface IProductFilterStrategy
{
IEnumerable<Product> Filter(IEnumerable<Product> products);
}

public class CategoryFilter : IProductFilterStrategy
{
private readonly string _category;

public CategoryFilter(string category)
{
_category = category;
}

public IEnumerable<Product> Filter(IEnumerable<Product> products)
{
return products.Where(p => p.Category == _category);
}
}

public class PriceFilter : IProductFilterStrategy
{
private readonly decimal _minPrice;
private readonly decimal _maxPrice;

public PriceFilter(decimal minPrice, decimal maxPrice)
{
_minPrice = minPrice;
_maxPrice = maxPrice;
}

public IEnumerable<Product> Filter(IEnumerable<Product> products)
{
return products.Where(p => p.Price >= _minPrice && p.Price <= _maxPrice);
}
}

测试驱动开发

在面试快结束的时候,架构师提到了测试用例这个话题,这差不多是最后一个问题了。

先进行测试能够确保你发现边界情况,并且能确切知道代码是否准确实现了你想要的功能。

以下是使用NUnit对我们的产品筛选功能进行的一个简单测试:

[TestFixture]
public class ProductFilterTests
{
[Test]
public void CategoryFilter_ShouldReturnOnlyMatchingProducts()
{
var products = new List<Product>
{
new Product { Name = "Laptop", Category = "Electronics", Price = 1000 },
new Product { Name = "Book", Category = "Books", Price = 20 }
};

var categoryFilter = new CategoryFilter("Electronics");
var filteredProducts = categoryFilter.Filter(products);

Assert.AreEqual(1, filteredProducts.Count());
Assert.AreEqual("Laptop", filteredProducts.First().Name);
}

[Test]
public void PriceFilter_ShouldReturnProductsWithinPriceRange()
{
var products = new List<Product>
{
new Product { Name = "Laptop", Category = "Electronics", Price = 1000 },
new Product { Name = "Book", Category = "Books", Price = 20 }
};

var priceFilter = new PriceFilter(50, 2000);
var filteredProducts = priceFilter.Filter(products);

Assert.AreEqual(1, filteredProducts.Count());
Assert.AreEqual("Laptop", filteredProducts.First().Name);
}
}

我发现通过先写测试用例,我的代码自然而然地变得更模块化、更易于维护了。

但除此之外,它还让我有信心确保后续的修改不会破坏现有的功能。

我最终没得到那份工作,但那次面试彻底改变了我对编码的看法。

从那以后,我开始编写清晰、可维护、可扩展的代码。更重要的是,我开始考虑谁会来阅读和维护我的代码了。

我更加刻意地去运用设计模式,妥善地处理错误,并且确保我的代码有足够的扩展性,能够满足未来的需求。我开始先写测试用例,并且更深入地思考边界情况以及意外的输入情况了。

如果你喜欢我的文章,请给我一个赞!谢谢

架构师老卢
资深软件架构师, 分享编程、软件设计经验, 教授前沿技术, 分享技术资源(每天发布电子书),每天进步一点点...
 最新文章