专栏首页韦弦的偶尔分享是什么使代码 “Swifty”? —— Safe

是什么使代码 “Swifty”? —— Safe

尽管编程语言是由其语法正式定义的,但实际上在实践中使用它们的方式还是可以由它们当前的约定来确定的。毕竟,就语法而言,大多数受“ C影响 ” 的语言看起来都非常相似,以至于您可以用几乎使它看起来像JavaScript,C#或C本身的方式编写Swift。

在Swift社区中,短语 "Swifty code" 通常用于描述遵循当前最流行的约定的代码。但是,尽管Swift的核心语法自最初引入以来并没有太大变化,但其约定随着时间的推移发生了巨大变化。

例如,许多Swift开发人员都记得从Swift 2到Swift 3的转换是语法方面的重大更改,但是这些更改中的大多数并不是真正的语法更改——它们是基于新集合对标准库API的更改命名约定。加上Swift 4对关键路径和Codable的介绍,Swift 5.1的函数生成器,属性包装器和不透明的返回类型,以及多年来引入的更多API和功能,并且开始变得很清楚,是什么使代码 “swifty” 是一个不断变化的目标。

本周,让我们仔细研究一下Swift的核心约定,以试图回答是什么真正使代码“ Swifty ” 的问题。

Swifty Code —— Safe

一致的目标(Aligned Goals)

在某种程度上,上述问题的简单答案可能是“与Swift的核心目标完全吻合的代码”。毕竟,尽管Swift的各种API,约定和语言功能会随着时间而变化,但它的基本目标基本保持不变——因此,如果我们能够以符合这些目标的方式编写自己的代码,那么我们将有更好的机会在任何给定的Swift上下文中使我们的代码看起来自然而清晰。

那么,这些目标到底是什么?Swift的官方网站上的About页面列出了三个关键字:

  • 安全(Safe):为了最大限度地减少开发人员的错误;
  • 迅速(Fast):执行的速度要快;
  • 表现力(Expressive):因为Swift的目标是尽可能清晰易懂。

是什么使代码 “Swifty”? —— Fast 介绍了如何利用系统的一些内置方法来提示性能 是什么使代码 “Swifty”? —— Expressive 介绍了如何使用表达性命名和API设计传达我们的代码意图

让我们来看看一些不同的事情,这些事情可能要牢记在心,以便使我们自己的代码遵循这些原则。

通过强大的类型安全保持清晰(Clarity through strong type safety)

让我们从第一个关键字开始——安全(Safe)。Swift非常重视类型安全性这一事实不容忽视——它具有静态类型检查,强大的泛型系统,以及编译时需要执行诸如类型擦除之类的操作才能使编译器能够验证我们的代码结构。

但是,遇到不是很明显可以改善我们代码的类型安全或使代码更加“Swifty”的情况是很常见的,例如,这里我们根据笔记所属的组的名称存储笔记的集合:

struct NoteCollection {
    var notesByGroup: [String : [Note]]
    ...
}

乍一看,上面的代码似乎很完美。但是,在查看上面的声明时,一个细节一点都不明显,那就是我们如何处理未分组的值,以及如何处理包含用户最近打开的所有便笺的特殊组——当前是通过传递一个空字符串或使用“recents”字符串来完成的:

let groupedNotes = collection.notesByGroup["MyGroup"]
let ungroupedNotes = collection.notesByGroup[""]
let recentNotes = collection.notesByGroup["recent"]

尽管上述设计可能有完全合理的理由(例如,我们使用的结构可能是通过网络加载笔记时如何组织笔记的结构),但这确实导致我们的某些调用变得非常隐秘——绕弯子会增加开发人员犯错的机会。很容易忘记,一个空字符串意味着应该检索所有未分组的笔记,如果用户将其自定义组之一命名为“recent”会怎样?

让我们看看是否可以使上面的代码更加安全,并使其更加“Swifty”。由于我们的notesByGroup字典具有三种不同的用例,因此,我们用一个自定义枚举替换其基于字符串的键,该枚举将这三种变体建模为不同的情形,如下所示:

enum Group: Hashable {
    case none
    case recent
    case named(String)
}

struct NoteCollection {
    var notesByGroup: [Group : [Note]]
    ...
}

上面的内容看似微小的变化,但是它使我们的调用更加清晰,因为我们现在利用类型系统来区分三种独立的组类型——所有这些都不会使我们的API变得更加复杂:

let groupedNotes = collection.notesByGroup[.named("MyGroup")]
let ungroupedNotes = collection.notesByGroup[.none]
let recentNotes = collection.notesByGroup[.recent]

这也许就是使代码在类型安全方面“Swifty”的本质。虽然有很多方法可以使API真正变得复杂以使其更加类型安全,但窍门是使用Swift的语言功能找到一种增加该类型安全性的方法,而又不会使我们的代码难以理解或使用。

虽然通常使用类型安全性来防止将类型B的值错误地传递给接受A的API,但是强类型化通常也提供了一种改善我们代码的语义和逻辑的方法。在下面的示例中,我们的代码在技术上是类型安全的——因为我们正在使用Swift的泛型功能来实现LoadingOperation,该LoadOperation可以加载符合Loadable协议的任何资源:

class LoadingOperation<Resource: Loadable> {
    private let resource: Resource

    init(resource: Resource) {
        self.resource = resource

        if let preloadable = resource as? Preloadable {
            preloadable.preload()
        }
    }
    
    ...
}

但是,我们有条件地强制转换资源以查看其是否也符合Preloadable(如果是,则预加载该资源)这一事实可以说有点奇怪。上面的实现不仅使我们很难理解如何进行资源预加载(因为类型系统没有给我们任何暗示我们应该遵循Preloadable的提示,以使这种情况发生),而且这样做非常不直观预加载是初始化操作的副作用

作为替代,让我们预加载一个明确的API,该API仅在操作的Resource符合Preloadable时才可用,如下所示:

extension LoadingOperation where Resource: Preloadable {
    func preload() {
        resource.preload()
    }
}

上面的更改都使我们更加清楚了预加载资源的条件,并且现在我们可以从初始化程序中消除类型转换的副作用——大赢家!需要注意的重要一点是,从安全角度出发编写“ Swifty”代码绝对不是尽可能多地使用泛型。而是要有选择地使用类型系统的各个方面和功能,以使我们的代码更易于理解和使用(更难于滥用)。

文章来自 John SundellWhat makes code “Swifty”?中关于Safe的内容

是什么使代码 “Swifty”? —— Fast 介绍了如何利用系统的一些内置方法来提示性能 是什么使代码 “Swifty”? —— Expressive 介绍了如何使用表达性命名和API设计传达我们的代码意图

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 在 Xcode 中添加 Swift package 依赖

    到目前为止,我们一直在编码的所有内容都是我们从头开始构建的内容,因此您可以确切地了解其工作原理并将这些技能应用于自己的项目。但是,有时候,从头开始写东西是有风险...

    韦弦zhy
  • 是什么使代码 “Swifty”? —— Fast

    是什么使代码 “Swifty”? —— Safe 介绍了如何有选择地使用类型系统的各个方面和功能,以使我们的代码更易于理解和使用。 是什么使代码 “Swif...

    韦弦zhy
  • 100 Days of SwiftUI —— Day 42:Moonshot(四)

    您现在已经完成了Moonshot,这是我们第一个开始变得困难的项目——需要花费更长的时间来解释,我们使用了自定义的SwiftUI布局,我什至还偷偷了解了一些高级...

    韦弦zhy
  • 是什么使代码 “Swifty”? —— Fast

    是什么使代码 “Swifty”? —— Safe 介绍了如何有选择地使用类型系统的各个方面和功能,以使我们的代码更易于理解和使用。 是什么使代码 “Swif...

    韦弦zhy
  • Postman 的替代品来了

    Postwoman 一个开源、免费、快速、漂亮的 API 构建器,可以替代 Postman。

    dys
  • Java 捕捉异常 finally代码块

    用户2965768
  • 如何利用Python批量下载百度图片?【附案例源码】

    有时候大家需要保存下载百度图片,但是在数量很大的情况下,一张一张地下载就显得很繁琐麻烦。那么,有没有一种方法可以把搜索到大量的百度图片直接下载到本地电脑中呢?当...

    python学习教程
  • 前端进阶之setTimeout 为什么会出现误差?

    讲到线程,那么肯定也得说一下进程。其实在本质上,两个名词都是 CPU 工作时间片的一个描述。

    陈大鱼头
  • Facebook 发布 Detectron2:基于 PyTorch 的新一代目标检测工具

    Detectron 是 FAIR 在 2018 年初公开的目标检测平台,包含了大量业内最具代表性的目标检测、图像分割、关键点检测算法,该框架主要基于 pytho...

    AI研习社
  • 如何在手机或 iPad 上写 Python 代码?

    或者用 iPad 外接一个键盘,侯爵老师一度迷恋这种工作方式,据说可以拥有更加专注的沉浸式编程体验。

    崔庆才

扫码关注云+社区

领取腾讯云代金券