简化iOS软件设计的四个规则

在1990年代后期,在开发极限编程时,著名的软件开发商Kent Beck提出了简单软件设计的规则列表。

根据肯特·贝克(Kent Beck)的观点,优秀的软件设计:

  • 运行所有测试
  • 不含重复
  • 表达程序员的意图
  • 减少类和方法的数量

在本文中,我们将通过提供实用的iOS示例并讨论如何从中受益,来讨论如何将这些规则应用于iOS开发领域。

运行所有测试

软件设计可以帮助我们创建一个按预期运行的系统。但是,我们如何验证系统最初将按照其设计预期运行?答案是通过创建验证它的测试。

不幸的是,在iOS开发中,大多数时候都避免进行Universe测试……但是,为了创建设计良好的软件,我们在编写Swift代码时应始终牢记可测试性。

让我们讨论两个可以简化测试编写和系统设计的原理。它们是单一责任原则和依赖注入。

单一责任原则(SRP)

SRP指出,一门课程应该只有一个,只有一个改变的理由。 SRP是最简单的原理之一,也是最难解决的原理之一。责任分担是我们自然而然的事情。

让我们提供一些很难测试的代码示例,然后使用SRP对其进行重构。然后讨论如何使代码可测试。

假设我们当前需要从当前视图控制器中显示一个PaymentViewController,那么PaymentViewController应该根据我们的付款产品价格来配置其视图。在我们的案例中,价格是可变的,具体取决于某些外部用户事件。

当前,此实现的代码如下所示:

我们如何测试此代码?我们首先应该测试什么?价格折扣是否正确计算?我们如何模拟付款事件以测试折扣?

为此类编写测试会很复杂,我们应该找到一种更好的编写方法。好吧,首先让我们解决这个大问题。我们需要解开依赖关系。

我们看到我们有逻辑来加载产品。我们有付款活动,使用户有资格享受折扣。我们确实有折扣,折扣计算,然后再继续。

因此,让我们尝试将它们简单地转换为Swift代码。

我们创建了一个PaymentManager来管理与付款相关的逻辑,并创建一个易于测试的单独PriceCalculator。另外,数据加载器负责网络或数据库的交互,以加载我们的产品。

我们还提到,我们需要一个负责管理折扣的课程。我们将其称为CouponManager并管理用户折扣优惠券。

然后,我们的“付款”视图控制器可能如下所示:

我们现在可以编写如下测试

  • testCalculatingFinalPriceWithoutCoupon
  • testCalculatingFinalPriceWithCoupon
  • testCouponExists

还有很多其他的!现在,通过创建单独的对象,我们避免了不必要的重复,并且还创建了易于编写测试的代码。

依赖注入

第二个原则是依赖注入。从上面的示例中我们已经看到,我们已经在对象初始化程序上使用了依赖注入。

像上面那样注入我们的依赖项有两个主要好处。它清楚说明了我们的类型所依赖的依赖关系,并允许我们在要测试时插入模拟对象,而不是真实的对象。

一项很好的技术是为我们的对象创建协议,并通过真实和模拟对象提供具体的实现,如下所示:

现在,我们可以轻松地决定要注入哪个类作为依赖项。

紧密耦合使编写测试变得困难。因此,类似地,我们编写的测试越多,就越会使用DIP之类的原理以及依赖注入,接口和抽象之类的工具来最大程度地减少耦合。

使代码更具可测试性,不仅消除了我们担心破坏代码的恐惧(因为我们将编写支持我们的测试),而且还有助于编写更简洁的代码。

与编写实际的单元测试相比,本文的这一部分更加关注如何编写可测试的代码。如果您想了解有关编写单元测试的更多信息,可以查看本文,其中我将使用测试驱动的开发来创造生活游戏。

不含重复

复制是设计良好的系统的主要敌人。它代表了额外的工作,额外的风险,增加了不必要的复杂性。

在本节中,我们将讨论如何使用模板设计模式来删除iOS中的常见重复项。为了使理解更容易,我们将重构现实生活中的聊天程序。

假设我们目前在我们的应用程序中有一个标准的聊天部分。提出了一个新的要求,现在我们要实现一种新型的聊天-在线聊天。聊天中最多应包含20个字符的消息,当我们关闭聊天视图时,该聊天将消失。

此聊天与我们当前的聊天具有相同的视图,但有一些不同的规则:

  1. 发送聊天消息的网络请求将有所不同。

2.聊天消息必须简短,消息不得超过20个字符。

3.聊天消息不应保留在我们的本地数据库中。

假设我们正在使用MVP体系结构,并且当前正在演示者中处理发送聊天消息的逻辑。让我们尝试为名为“实时聊天”的新聊天类型添加新规则。

天真的实现将如下所示:

但是,如果将来我们将拥有更多的聊天类型会怎样?
如果我们继续添加其他功能来检查每个函数中的聊天状态,则代码将变得难以阅读和维护。此外,它几乎不可测,状态检查会在演示者的整个范围内重复进行。

这是使用模板模式的地方。当我们需要多种算法实现时,可以使用模板模式。定义模板,然后以进一步的变化为基础。当大多数子类需要实现相同的行为时,请使用此方法。

我们可以为Chat Presenter创建一个协议,并分离在Chat Presenter阶段中具体对象将以不同方式实现的方法。

现在,我们可以使演示者符合IChatPresenter

现在,演示者可以通过调用自身内部的常用功能来处理消息发送,并委派可以以不同方式实现的功能。

现在,我们可以提供创建符合演讲者阶段的对象,并根据需要配置这些功能。

如果我们在视图控制器中使用依赖注入,则现在可以在两种不同情况下重用同一视图控制器。

通过使用设计模式,我们可以真正简化我们的iOS代码。如果您想进一步了解,下面的文章提供了进一步的解释。

富有表现力的

软件项目的大部分成本用于长期维护。编写易于阅读和维护的代码对于软件开发人员来说是必须的。

通过使用良好的命名,使用SRP和编写测试,我们可以提供更具表现力的代码。

命名

使代码更具表现力的第一件事就是命名。重要的是要写出以下名称:

  • 揭示意图
  • 避免虚假信息
  • 易于搜索

关于类和函数的命名,一个好技巧是对类使用名词或名词短语,对方法使用用户动词或动词短语名称。

同样,当使用不同的设计模式时,有时最好在类名称中附加模式名称,例如Command或Visitor。因此,读者将立即知道在那里使用了什么模式,而无需阅读所有代码来了解它。

使用SRP

使代码更具表现力的另一件事是使用上面提到的“单一职责原则”。您可以通过使函数和类的大小较小并出于一个单一目的来表达自己。小型类和函数通常易于命名,易于编写和易于理解。功能只能用于一个目的。

写作测试

编写测试还带来了很多清晰度,尤其是在处理旧版代码时。编写良好的单元测试也很有表现力。测试的主要目标是通过示例充当文档。读过我们的测试的人应该能够快速了解​​类的全部内容。

减少类和方法的数量

一个类的功能必须保持简短,一个功能应始终仅执行一件事。如果某个函数的行太多,则可能是该函数执行的动作可能被分为两个或多个单独的函数。

一个好的方法是计算物理行,并尝试针对最多四到六行功能,在大多数情况下,任何超出该行数的内容都将变得难以阅读和维护。

iOS中的一个好主意是砍掉我们通常在viewDidLoad或viewDidAppear函数上进行的配置调用。

这样,每个函数将很小且可维护,而不是一个混乱的viewDidLoad函数。同样也应适用于应用程序委托。我们应该避免在ondidFinishLaunchingWithOptions方法上抛出每个配置,并避免使用单独的配置函数甚至更好的配置类。

使用函数,可以更轻松地衡量我们将其保留为长还是短,在大多数情况下,我们可以仅依靠对物理线进行计数。对于类,我们使用不同的度量。我们计算责任。如果一个类只有五个方法,这并不意味着该类很小,这可能是因为它仅对那些方法负有太多责任。

iOS中的一个已知问题是UIViewControllers的尺寸过大。确实,通过Apple View Controller设计,很难将这些对象用于单一目的,但我们应该尽力而为。

有很多方法可以使UIViewControllers变小,我更喜欢使用一种架构,这种架构可以更好地分离关注点,例如VIPER或MVP,但这并不意味着我们也不能在Apple MVC中做得更好。

通过尝试分离尽可能多的关注点,我们可以使用任何体系结构获得相当不错的代码。这个想法是创建单一用途的类,这些类可以充当视图控制器的助手,并使代码更具可读性和可测试性。

在视图控制器中没有任何借口可以简单避免的一些事情是:

  • 而不是直接编写网络代码,应该有一个NetworkManager类负责网络调用
  • 代替在视图控制器中操作数据,我们可以简单地创建一个DataManager类来负责。
  • 可以在UIViewController上创建一个外观,而不是在UIViewController中使用UserDefaults字符串。

结论

我认为,我们应该使用准确命名,简单,小型,负责一件事且可重用的组件来组成软件。

在本文中,我们讨论了肯特·贝克(Kent Beck)的四个简单设计规则,并给出了如何在iOS开发环境中实现这些规则的实际示例。

如果您喜欢本文,请确保鼓掌以表示支持。跟随我查看更多文章,这些文章可以使您的iOS开发人员技能更上一层楼。

如果您有任何疑问或意见,请随时在此处留言或发送电子邮件至arlindaliu.dev@gmail.com。