简化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上创建生活游戏
medium.com
不含重复
复制是设计良好的系统的主要敌人。 它代表了额外的工作,额外的风险,增加了不必要的复杂性。
在本节中,我们将讨论如何使用模板设计模式来删除iOS中的常见重复项。 为了使理解更容易,我们将重构现实生活中的聊天程序。
假设我们目前在我们的应用程序中有一个标准的聊天部分。 提出了一个新的要求,现在我们要实现一种新型的聊天-在线聊天。 聊天中最多应包含20个字符的消息,当我们关闭聊天视图时,该聊天将消失。
此聊天与我们当前的聊天具有相同的视图,但有一些不同的规则:
- 发送聊天消息的网络请求将有所不同。
2.聊天消息必须简短,消息不得超过20个字符。
3.聊天消息不应保留在我们的本地数据库中。
假设我们正在使用MVP
体系结构,并且当前正在演示者中处理发送聊天消息的逻辑。 让我们尝试为名为“实时聊天”的新聊天类型添加新规则。
天真的实现将如下所示:
但是,如果将来我们将拥有更多的聊天类型会怎样?
如果我们继续添加else
来检查每个函数中的聊天状态,则代码将变得难以阅读和维护。 而且,它几乎不可测试,并且状态检查将在演示者的整个范围内重复进行。
这是使用模板模式的地方。 当我们需要多种算法实现时,可以使用模板模式。 定义模板,然后以进一步的变化为基础。 当大多数子类需要实现相同的行为时,请使用此方法。
我们可以为Chat Presenter创建一个协议,并分离在Chat Presenter阶段中具体对象将以不同方式实现的方法。
现在,我们可以使演示者符合IChatPresenter
现在,演示者可以通过调用自身内部的常用功能来处理消息发送,并委派可以以不同方式实现的功能。
现在,我们可以提供创建符合演讲者阶段的对象,并根据需要配置这些功能。
如果我们在视图控制器中使用依赖注入,则现在可以在两种不同情况下重用同一视图控制器。
通过使用设计模式,我们可以真正简化我们的iOS代码。 如果您想进一步了解,下面的文章提供了进一步的解释。
使用设计模式简化iOS代码
在软件开发中,设计模式是解决问题的通用方法。 一种设计模式是… medium.com
富有表现力的
软件项目的大部分成本用于长期维护。 编写易于阅读和维护的代码对于软件开发人员来说是必须的。
通过使用良好的命名,使用SRP和编写测试 ,我们可以提供更具表现力的代码。
命名
使代码更具表现力的第一件事就是命名。 重要的是写以下名称:
- 揭示意图
- 避免虚假信息
- 易于搜索
关于类和函数的命名,一个好技巧是对类使用名词或名词短语,对方法使用用户动词或动词短语名称。
同样,当使用不同的设计模式时,有时最好在类名称中附加模式名称,例如Command或Visitor。 因此,读者将立即知道在那里使用了什么模式,而无需阅读所有代码来了解它。
使用SRP
使代码更具表现力的另一件事是使用上面提到的“单一职责原则”。 您可以通过使函数和类的大小较小并出于一个单一目的来表达自己。 小型类和函数通常易于命名,易于编写和易于理解。 功能只能用于一个目的。
写作测试
编写测试还带来了很多清晰度,尤其是在处理旧版代码时。 编写良好的单元测试也很有表现力。 测试的主要目标是通过示例充当文档。 读过我们的测试的人应该能够快速了解类的全部内容。
减少类和方法的数量
一个类的功能必须保持简短,一个功能应始终仅执行一件事。 如果一个函数的行太多,则可能是它正在执行的动作可能被分为两个或多个单独的函数。
一个好的方法是计算物理行,并尝试针对最多四到六行功能,在大多数情况下,任何超出该行数的内容都将变得难以阅读和维护。
iOS中的一个好主意是砍掉我们通常在viewDidLoad
或viewDidAppear
函数上进行的配置调用。
这样,每个函数将很小且可维护,而不是一个混乱的viewDidLoad
函数。 同样也应适用于应用程序委托。 我们应该避免将所有配置都扔在didFinishLaunchingWithOptions
方法上,并避免使用单独的配置函数甚至更好的配置类。
使用函数,可以更容易地衡量我们将其保留为长还是短,在大多数情况下,我们可以仅依靠对物理线路的计数。 对于类,我们使用不同的度量。 我们计算责任。 如果一个类只有五个方法,这并不意味着该类很小,这可能是因为它仅对那些方法负有太多责任。
iOS中的一个已知问题是UIViewControllers
尺寸过大。 确实,通过Apple View Controller设计,很难将这些对象用于单一目的,但我们应该尽力而为。
有很多方法可以使UIViewControllers
小,我更喜欢使用一种架构,该架构可以更好地分离关注点,例如VIPER
或MVP
但这并不意味着我们也无法在Apple MVC
中将其更好。
通过尝试分离尽可能多的关注点,我们可以使用任何体系结构获得相当不错的代码。 这个想法是创建单一用途的类,这些类可以充当视图控制器的助手,并使代码更具可读性和可测试性。
在视图控制器中没有任何借口可以简单避免的一些事情是:
- 而不是直接编写网络代码,应该有一个
NetworkManager
类负责网络调用 - 代替在视图控制器中操作数据,我们可以简单地创建一个
DataManager
类来负责。 - 可以在
UIViewController
上创建一个外观,而不是在UIViewController
使用UserDefaults
字符串。
结论
我认为,我们应该使用准确命名,简单,小型,负责一件事且可重用的组件来组成软件。
在本文中,我们讨论了肯特·贝克(Kent Beck)的四个简单设计规则,并给出了如何在iOS开发环境中实现这些规则的实际示例。
如果您喜欢本文,请确保鼓掌以表示支持。 跟随我查看更多文章,这些文章可以使您的iOS开发人员技能更上一层楼。
如果您有任何疑问或意见,请随时在此处留言或发送电子邮件至arlindaliu.dev@gmail.com。