当前位置: 首页 > 科技观察

17Swift开发规范最佳实践

时间:2023-03-13 16:57:50 科技观察

这篇文章基于我在SwiftGraphics工作时做的一系列笔记。本文中的大部分建议都经过深思熟虑,但还有其他类似的解决方案。因此,如果其他选项有意义,将添加这些选项。此最佳实践无意强加或推荐以过程化、面向对象或函数式风格使用Swift。更重要的是,这里讲述的是一种务实的方法。如有必要,一些建议可能会侧重于面向对象或实用的解决方案。本文的范围主要针对Swift语言和Swift标准库。即便如此,如果能够呈现出独特的Swift视角和见解,我们仍会提供特别的建议,例如在MacOS、iOS、WatchOS和TVOS上使用Swift。还以提示和技巧的形式提供了有关如何有效地将Swift与Xcode和LLDB结合使用的建议。这个过程需要付出很多努力,非常感谢那些为本文做出贡献的人。另外,可以在[Swift-Langslack]中讨论。贡献者注意事项请确保所有示例都有效(某些示例可能不正确)。这个markdown可以转换成MacOSXplayground。黄金法则通常是Apple做对了,遵循Apple喜欢或展示的东西。在所有情况下,您都应该遵循Apple的编码风格,如他们的书“TheSwiftProgrammingLanguage”中所定义的那样。然而Apple是一家大公司,我们会在示例代码中看到很多不同之处。永远不要仅仅为了减少代码量而编写代码。尝试依靠Xcode中的自动完成代码、自动建议、复制和粘贴。详细的代码描述风格对其他代码维护者非常有益。即便如此,过多的冗余可能会失去Swift的一个重要特性:类型推断。最佳实践1.命名就像Swift编程语言中的类型名称是用大驼峰命名的(例如:VehicleController)。变量和常量以驼峰命名法命名(例如:vehicleName)。您应该使用Swift模板来命名您的代码,而不是使用Objective-C类前缀样式(除非它连接到Objective-C)。不要使用任何匈牙利符号(例如:k表示常量,m表示方法),使用短名称并使用Xcode的类型快速帮助(01.png+单击)找出变量的类型。同样,不要使用小写字母+下划线(SNAKE_CASE)来命名。唯一比较特殊的是枚举值的命名,这里需要使用大驼峰的命名方式(这也是遵循Apple的Swift编程语言风格):enumPlanet{caseMercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune}在所有可能的情况下都应避免不必要的名称缩减和缩写,并且在未来您应该能够准确地指出类型特征“ViewController”而不会造成任何伤害并且不依赖于Xcode的自动完成。允许使用非常常见的缩写,例如URL。缩写应全部大写(URL)或全部小写(url)。对类型和变量使用相同的规则。如果url是一个类型,它应该是大写的,如果它是一个变量,它应该是小写的。2.注释注释不应该用来使代码失效,注释代码会使代码失效,影响代码的整洁。如果你想删除代码,但仍想保留它以备日后需要,你应该依赖git或错误跟踪器。3.类型推断尽可能使用Swift的类型推断来减少冗余的类型信息。例如,正确的写法是:varcurrentLocation=Location()而不是:varcurrentLocation:Location=Location()4.自我推断让编译器在所有允许的地方进行自我推断。在init和非转义闭包中设置参数时,应显式使用self。例如:structExample{letname:Stringinit(name:String){self.name=name}}5.参数列表类型推断在闭包表达式(closureexpression)中指定参数类型可能会导致代码更冗长。仅当需要指定类型时。letpeople=[("Mary",42),("Susan",27),("Charlie",18),]letstrings=people.map(){(name:String,age:Int)->Stringinreturn"\(name)is\(age)yearsold"}如果编译器能够推断出类型,它应该去掉类型定义。letstrings=people.map(){(name,age)inreturn"\(name)is\(age)yearsold"}最好用排序后的参数编号命名为("$0","$1","$2")来最小化冗余,这通常会导致参数列表完全匹配。仅当闭包的参数名称中没有太多信息时才使用编号名称。(例如非常简单的地图和过滤器)。Apple可以并且将会更改从Objective-C框架转换而来的Swift的参数类型。例如,选项被删除或自动扩展等。我们应该有意识地指定您的选项并依靠Swift来推断类型,以减少在这种情况下程序中断的风险。您应该始终谨慎地指定返回类型。比如这个参数列表显然过于冗余:dispatch_async(queue){()->Voidinprint("Fired.")}6.当常量定义在类型中时,应该在类型中将常量声明为static。例如:structPhysicsModel{staticvarspeedOfLightInAVacuum=299_792_458}classSpaceship{staticlettopSpeed=PhysicsModel.speedOfLightInAVacuumvarspeed:DoublefuncfullSpeedAhead(){speed=Spaceship.topSpeed}}使用静态修饰符常量允许在不实例化类型的情况下引用它们。除了单例,你应该尽量避免生成全局常量。7.计算属性(ComputedProperties)当你只需要继承getter方法时,返回一个简单的Computed属性。例如,你应该这样做:classExample{varage:UInt32{returnarc4random()}}而不是:classExample{varage:UInt32{get{returnarc4random()}}}如果你在属性中添加set或didSet,那么你应该显示Provide获取方法。classPerson{varage:Int{get{returnInt(arc4random())}set{print("That'snotyourage.")}}}8.转换实例当创建代码以从一种类型转换为另一种init()方法时:extensionNSColor{convenienceinit(_mood:Mood){super.init(color:NSColor.blueColor)}}在Swift标准库中,为了将一种类型的实例转换为另一种类型的实例,似乎首选使用init方法。“to”方法是另一种合理的技术(尽管您应该遵循Apple的指导使用init方法):structMood{functoColor()->NSColor{returnNSColor.blueColor()}}并且您可以尝试使用getter,例如:structMood{varcolor:NSColor{returnNSColor.blueColor()}}getter通常受到限制,因为它们应该返回可接受类型的组件。例如,使用getter返回一个Circle实例完全没问题,但是将Circle转换为CGPath最好使用“to”函数或CGPath上的init()扩展来完成。#p#9。Swift中的单例非常简单:classControversyManager{staticletsharedInstance=ControversyManager()}Swift的运行时将确保创建单例并以线程安全的方式访问它们。单例通常只需要访问“sharedInstance”静态属性,除非您有令人信服的理由重命名它。请注意,不要使用静态函数或全局函数来访问您的单例。(因为单例在Swift中太简单了,不断的命名又浪费了你太多的时间,你应该有更多的时间去抱怨为什么单例是一种反模式的设计,但是要避免花太多时间,你的同行会感谢你的。)10.使用扩展来组织代码应该使用扩展来组织代码。实例的次要方法和属性应移至扩展中。请注意,目前并非所有属性类型都支持迁移到扩展,要做到最好,您应该在此限制内使用扩展。您应该使用扩展来帮助组织您的实例定义。一个很好的例子是扩展表视图数据源和委托协议的视图控制器。为了尽量减少表视图中的代码,将数据源和委托方法集成到扩展中以适配相应的协议。在单个源文件中,根据您认为组织代码的最佳方式向扩展添加定义。不必担心扩展主类的方法或指向方法和属性的结构中定义的方法。只要所有文件都包含在一个Swift文件中,就可以了。相反,main的实例定义不应指向主Swift文件范围之外的扩展中定义的元素。11.链式设置器对于简单的设置器属性,不要使用链式设置器作为方便的替代方法。正确方式:instance.foo=42instance.bar="xyzzy"错误方式:instance.setFoo(42).setBar("xyzzy")与链式setter相比,传统setter更简单,不需要过多的公式化。12.错误处理Swift2.0的do/try/catch机制很棒。13.避免使用try!通常,使用以下内容:do{trysomethingThatMightThrow()}catch{fatalError("Somethingbadhappened.")}而不是:try!somethingThatMightThrow()尽管这种形式特别冗长,但它提供了上下文使此代码可供其他人检查开发商。在更详细的错误处理策略出来之前,对待try还是可以的!作为临时错误处理。但建议您定期检查您的代码以发现任何非法尝试!这可能会逃避您的代码检查。14.避免使用try?尝试?是用来“抑制”错误的,只有当你确定自己不关心错误的产生时,试试?很有用。通常,您应该捕获错误并至少将它们打印出来。15.过早返回&Guards如果可能,使用guard语句来处理过早返回或其他退出条件(例如,致命错误或thorwn错误)。正确的写法:guardletsafeValue=criticalValueelse{fatalError("criticalValuecannotbenilhere")}someNecessaryOperation(safeValue)错误的写法:ifletsafeValue=criticalValue{someNecessaryOperation(safeValue)}else{fatalError("criticalValuecannotbenilhere")}或者:ifcriticalValue==nil"criticalValuecannotbenilhere")}someNecessaryOperation(criticalValue!)这个扁平代码否则会进入一个iflet代码块,并在接近相关的环境中过早退出,而不是进入else代码块。即使您没有捕获值(guardlet),此模式也会在编译期间强制提??前退出。在第二个if示例中,即使代码像守卫一样变平,灾难性错误或以其他方式返回一些无法退出的进程(或非法状态,具体取决于具体实例)将导致崩溃。当发生过早退出时,guard语句会及时捕获错误并将其从else块中移除。16.“早期”访问控制即使你的代码没有被分成独立的模块,你也应该始终考虑访问控制。将定义标记为私有或内部充当代码的轻量级文档。看过代码的人都会知道,这个元素是不能“碰”的。相反,将一个元素定义为public等同于邀请其他代码访问该元素。我们最好明确指定而不是依赖Swift的默认访问控制级别。(内部)如果你的代码库在未来增长,它可能会被分解成子模块。这样做可以使已经装饰有访问控制信息的代码库更加方便和快捷。17.限制性访问控制一般来说,在给你的代码添加访问控制时,最好有详细的限制。在这里,使用private比internal更有意义,使用internal显然比public好。(注意:内部是默认值)。如有必要,很容易使代码的访问控制更加开放(大致如下:“私有”到“内部”再到“公共”)。过于开放的访问控制代码可能不适合其他代码使用。具有足够限制性的代码能够检测到不适当和错误的使用,并提供更好的界面。一个示例是公开公开内部缓存的类型。此外,对代码的受限访问限制了“暴露的表面积”,并允许重构代码而对其他代码的影响较小。其他技术如:ProtocolDrivenDevelopment也可以起到同样的作用。TODO部分这是未来可能扩展的标题列表。协议和协议驱动开发隐式解包选项引用与值类型异步闭包无主与弱Cocoa委托不可变结构实例初始化日志记录和值打印融合类型融合类型