几个步骤,让你的 iOS 代码容易阅读

本文翻译自 Making your iOS application easy to read with these simple steps.ios


优秀的程序员会用尽量简单的方式来解释他们的代码,即便是物理学家均可以用一张白纸和一只铅笔来解释虫洞,咱们又未尝不可?程序员

我会尽量让代码写地简单、易读,包括选择合适的变量名、使用编码规范(code conventions)等等,但仍是缺了点东西,理解代码不该该是去理解“如何”实现的,而是要理解想要“达成”什么。bash

甚至能够说要让读代码像读小说同样,而不是一大堆代码。app


下面讨论三大主题:ide

问题

阅读其余人的代码可能会很是折磨,若是不提供合适的上下文,咱们会迷失在寻找某个函数或属性的意义中。函数

建议

不管是二进制语言、低级语言仍是高级语言,语法都在变得愈来愈友好,以便吸引更多开发者。而随着语法变得更接近英语,咱们的代码也应该简明扼要、不言自明。布局

结果

写出良好的代码,读起来像小说同样,容易阅读和理解(即便没有给出上下文)。ui


函数命名

正确的方式是:编码

咱们写函数时都会假设阅读它的人拥有足够的上下文,可以理解函数想实现什么。用模糊的含义来命名函数,例如“handleRedView()”会引发不少问题,”RedView”是啥?这个函数主要是想干啥?spa

因此在某些状况下函数的用途会很模糊,若是没有提供足够的上下文,阅读起来会很是困难

咱们能够把函数的用途分为四类:

  1. 通知(Informer)函数
  2. 管理(Management)函数
  3. 路由(Router)函数
  4. 执行(Execution)函数

1. 通知函数

一般会触发路由/管理函数,例子以下:

delegate.dataHasUpdated()

func dataHasUpdated()
{
  //通知某事的发生
 }

// 通知函数
override func engineStarted()
{
  super.engineStarted()
  handleCarStarted()
 }
复制代码

回调函数,通知某事已经/即将发生,并给机会进行响应。

大部分状况下用于被代理(delegate)触发的操做,或是通知(notification)处理函数。

2. 管理函数

用于联合多个函数以实现更高级的用途,不须要依赖参数,block 中的全部代码都会执行。

// 管理函数
func handleCarStarted()
{
  turnLights(on: true)
  turnAC(on: true)
}
复制代码

上面的函数包含全部须要的信息,汽车启动时执行这些函数,此时咱们不关注这是“如何”实现的,而是关注它作了“什么”。

3. 路由函数

用于联合多个函数以实现更高级的用途,须要依赖一些参数,只在咱们想执行的时候才执行。

// 路由函数
private func turnLights(on shouldTurnLightsOn: Bool)
{
  if shouldTurnLightsOn
 {
   turnExteriorLightsOn()
   checkForBurnedBulbs()
 }
 else { turnExteriorLightsOff() }
 若是 if 语句只执行一件事,我喜欢把 "if" "else" 和执行语句写在同一行,这样代码读起来会更流畅。
复制代码

路由函数一般同于指向执行函数,但在某些状况下,若是逻辑代码不超过一行,也能够包含本身的逻辑。

4. 执行函数

函数名字的具体实现

// 执行函数
private func turnExteriorLightsOn()
{
  leftFrontLight.isOn = true
  rightFrontLight.isOn = true
  leftBackLight.isOn = true
  rightBackLight.isOn = true
 }
 private func checkForBurnedBulbs()
 {
   for lightBulb in bulbs where !lightBulb.isUseable
   {
     Dashboard.(errorType: .lights)
     break
   }
 }
复制代码

这样就能写出一个干净、简短的类,可读性强,容易维护。


* 谨记一项原则,每一个函数都应该只有一个责任。

避免在函数名称里使用”and“: playAndMinimize() loadAndPlay() 这个坏习惯会打破单一责任原则,写出可以适应两种状况的代码。

避免在函数名称里进行猜想: moveRedViewIfNeeded() 上面的例子会致使后面的程序员必须深刻此函数,才能理解触发移动 Red View 的时机,这样不够清晰。

不,layoutIfNeeded() 并不属于这种状况,由于咱们知道若是某个 view 的 setNeedDisplay 为 true,就应该从新布局。这种状况在 Swift 语言里很广泛,由于函数基本上都是应用私有的。


谈及代码可读性,我首先会想到编码规范(code convention),它们被广泛接受、应用普遍,但使用编码规范并不必定会提高代码质量,虽然有跨应用性但可读性更差。

”is“前缀应该用于布尔型变量和方法,以便解释返回值是布尔类型的。#编码规范

既然”if“语句老是用于布尔值,那还有必要给每一个布尔属性都加上”is“吗?为何苹果要把 Swift 语法从 view.hidden 改为 view.isHidden?我只能想到一种答案……由于**“if view.isHidden”**看起来更天然。

尝试以如下原则使用“is”前缀:

  • 若是类的某个布尔属性/方法用于该类(公开)的实例,就有正当理由使用“is”前缀。
public var isHidden: Bool
{
  return alpha == 0.0
 }
 if containerView.isHidden
复制代码
  • 若是布尔属性在类内部(私有)使用,前缀就是多余的。
private var positionedVerticaly: Bool
 {
   return view.frame.width/2 == centerX
 }
 if positionedVerticaly
 if positionedVerticaly && positionedHorizontally
 VS
 if isPositionedVerticaly
 if isPositionedVerticaly && isPositionedHorizontally
复制代码
  • 若是布尔型属性/方法同时被私有公开使用,那么应该用计算属性来返回该私有属性值。
public var isPositionedVerticaly: Bool
{
  return positionedVerticaly
 }
 if containerView.isPositionedVerticaly
复制代码

虽然用私有 set 并公开使用该属性也是能够的,但上面这种方式能够实现封装(encapsulation)。

封装用于隐藏类中结构化数据对象的值或状态,防止未受权方直接访问。

en.wikipedia.org/wiki/Encaps…

那若是不是本身直接处理的布尔型,若是如何命名呢?

private var playerIsPlaying: Bool

private var gridConstraintIsEnabled()

“is” 须要指向某个东西:view.isHidden, “is”指向 view。上面的例子里使用了此原则,playerIsPlaying,“is”指向 player。

谨记:开发者一般会在阅读属性声明以前先阅读函数内部的代码,尝试搞明白这些属性的用途。

/if playerIsPlaying { }/ 对比 /if isPlayerIsPlaying {}/
复制代码

哪一个更天然?我想你已经有答案了。

相关文章
相关标签/搜索