Tag: 干净代码

Swift解决方案:复合

复合模式涉及集合和单个对象的树层次结构。 在上面的插图中,我们有一个文件夹和mp3的目录。 文件夹仅包含文件数组。 每个文件夹的集合可以包含mp3和其他文件夹的混合。 该插图很好地对应于我们定义的每个部分: 层次结构->目录 收集对象->文件夹 单个对象-> mp3文件 它允许客户以相同的方式对待每个层次结构元素。 层次结构中的所有文件共享一个公共接口,而不管它是文件夹还是mp3。 例如,mp3和文件夹都是可以重命名,移动和复制的文件。 因此,可以合理地期望符合要求实现类文件行为的协议。 重要的一点是,客户可以统一对待小组和单个部分。 在使用每个文件之前,无需经常对其进行类型检查。 这是使元素派生自相同协议或基类的直接结果。 UML 组件 :为集合和单个对象提供公共接口的抽象。 树中的所有元素都必须派生自组件协议或基类。 基元 :以前称为“单个对象”。基元只是树中不包含子级成分的成分。 Composite :以前称为“集合对象”。Composite是包含组件数组的对象。 虽然Composite对象和Primitive对象共享相同的接口,但是Composites包含其他方法来管理其子级。 请注意:有几种方法可以引用我们的具体组件: 我个人更喜欢“复合”和“原始”这两个术语。接下来,让我们进入代码吧! 实作 在我们的示例中,我们将向公司部门分配奖金,最终将其发放给员工。 在这种情况下,部门和员工分别充当我们的组合和基元。 零件 protocol Payee { var name: String { get } var bonusAmount: Double { get } func receive(bonus: Double) } 首先,我们为复合对象和原始对象创建一个Payee协议。 这将是我们统一对待他们的手段。 雇员 class Employee: […]

Swift和iOS良好做法#12中代码质量和简洁性的提示

导入SomeExternalFramework FooViewController类:UIViewController,FoobarDelegate { let foo:富 private let fooStringConstant =“ FooConstant” 私人让floatConstant = 1234.5 // MARK:生命周期 //自定义初始化程序转到此处 // MARK:查看生命周期 覆盖func viewDidLoad(){ super.viewDidLoad() // … } // MARK:布局 私人函数makeViewConstraints(){ // … } // MARK:用户互动 func foobarButtonTapped(){ // … } //标记:FoobarDelegate func foobar(foobar:Foobar,didSomethingWithFoo foo:Foo){ // … } // MARK:其他助手 私人功能displayNameForFoo(foo:Foo){ // … } } 如果您不打算对一个类进行子类化,请将其定为final 。 对tableview进行两个扩展 (一个用于数据源,另一个用于委托方法)。 […]

软件工程师的角色-专业咨询的明确内容

在我担任软件工程师的整个过程中,尤其是在过去的一年中,我遇到了许多想法,概念,良好实践,设计模式,体系结构,思想流等等。 对于任何专业开发人员来说,自然都对如何制作更好的软件产生了兴趣。 经过大量的讨论,研究和更多的经验,直到最近才让我想到,所有这些都可以用几个简单的词来概括:“您必须明确”。 建筑 让我们从所有人一直以来最喜欢的架构开始。 在软件开发方面,软件体系结构显然是极其重要的主题。 在整个编程历史中,出现了许多旨在帮助您拥有最佳架构的原理和设计模式。 但是,无论您的信念或偏好是什么,它们都倾向于相同的目标: 明确 。 无论我们谈论的是Clean Architecture , VIPER , MVVM , MVC还是任何其他形式,它们的唯一目标是帮助您弄清自己在做什么和如何做。 在一些会议上,我从罗伯特·C·马丁(Robert C. Martin)(鲍勃叔叔)那里看到了他的例子,此刻一直困扰着我。 让我问你一件事,向你证明这一点。 你能告诉我下图是什么吗? “ 好吧,这不是很明显吗? 您可以告诉我,“ 这是大教堂的蓝图! ”。 我假设您不是我还是建筑建筑师或房地产专业人士,但是,我们所有人仍然能够马上说出那些是大教堂的建筑计划。 当我们看到它时,它尖叫起来就是为了建造一座大教堂。 那么,在软件体系结构方面,为什么要有所不同呢? 如果您的体系结构很棒,那么对任何打开您的项目的人来说,它应该立即变得显而易见。 哦! 这显然是银行帐户管理应用程序! ” “ 哦,很明显,这是一个在线书店应用程序! ”。 如前所述,无论您最喜欢的概念或设计模式是什么,在拥有良好的软件体系结构时,都可以用以下几句话来概括:“ 您必须明确 ”。 行为驱动的发展 BDD是一种软件开发过程,其灵感来自于测试驱动开发(TDD)和其他来自面向对象编程的设计概念。 在Dan Dan的带领下,这一过程在过去几年中越来越受欢迎,而我有机会在上一个项目中进行实践。 BDD的目标是通过一起指定业务需求来帮助项目的开发人员和业务人员进行协作,并编写测试以验证软件中的那些业务需求。 这样做有助于在项目的业务和技术方面都清楚应用程序应该做什么以及应用程序是否正常工作。 这可以用几句话来概括:“ 您必须明确 ”。 域驱动设计 DDD是由Eric Evans创建或至少明确定义的软件开发方法。 它的目的是将项目的重点放在领域及其逻辑上。 这样,它允许程序员,项目经理,利益相关者和项目中涉及的任何其他人共享使用普遍存在的语言讨论的领域的相同愿景,从而彼此理解。 […]

Swift中的变形金刚

变形金刚? 直到最近,每当我听到“功能是Swift中的头等公民!” ,我希望将闭包作为方法中的回调或完成块传递,也许是要从异步网络调用中获取一些数据,如下所示: func fetchUsers(_补全:@转义([用户])->无效){ // .. } 或将代码块分配给变量: 让闭包= { 令a = 1 + 2 + 3 打印(a) } 真好 我们也知道Swift的map和其他高阶函数将闭包作为参数。 多进行一些挖掘,这将打开一个门户,进入另一个维度,即编写代码的功能方式。 等等,暂时不要下载RxSwift,我们可以从这里开始一些基础知识。 我对函子/单子的可疑理论 原来, map , flatMap , reduce之类的东西代表了Swift的功能风格。 Haskell是一种纯函数式语言,在Haskell的fmap和liftM之间直接映射到Swift的map和flatMap 。 这些实体称为函子或单子 。 这些来自类别理论。 如果我被迫开枪简要介绍我对该概念的非常有限的理解,我会说, 我们对基本类型执行操作的方式,函子/单子使我们即使在上下文中也可以对此类进行操作。 例如: func addTwo(int:Int)-> Int { 返回int + 2 } 让三个= addTwo(int:1)// 3 让threeOrMore = [1,2,3] .map(addTwo)// [3,4,5] […]

代码结构和可读性第2部分-类和结构

欢迎回来,在第1部分中,我们介绍了一些基础知识:变量,数据结构和函数。 代码结构和可读性第1部分—变量,数据结构和函数 如果您来自我的有关结构和可读性的Swift编程文章,那么您已经知道一些基本知识…… medium.com 我们学会了一些清理它们的方法,但是现在我们继续前进。 我计划这次对您轻松一点,因为我们在这里只讨论类和结构。 如果您不了解类或结构,建议您阅读我为Swift写的一篇文章。 不用担心Swift不是您的语言,如果您的语言支持它们,那么足够通俗易懂,足以让您掌握类和结构。 类和结构 让我们从那里抛出一个示例类开始。 公共类动物{ 命名:字符串 让收藏夹食物:字符串 变量年龄:整数 让countryOfOrigin:字符串 init(名称:字符串,最喜欢的食物:字符串,年龄:整数, countryOfOrigin:字符串){ self.name =名称 self.favoriteFood =最喜欢的食物 自我年龄=年龄 self.countryOfOrigin = countryOfOrigin } } 当然,我们可以将其更改为struct并忘记初始化程序,但是我要使用一个类,因为让我们面对现实吧,即使您尝试使其具有可读性,也有点草率。 老实说,当结构可以使用时,我尽量不要使用类。 我不必担心初始化(除非我想),我不必担心参考周期(除非我要进行极端优化),并且我的代码库要小几行。 那么我们可以使用类来解决这个问题吗? 好吧,确定间距确实有帮助,所以让我们从这里开始: 公共类动物{ 命名:字符串 让收藏夹食物:字符串 var age =年龄 让countryOfOrigin:字符串 init(名称:字符串,最喜欢的食物:字符串,年龄:整数, countryOfOrigin:字符串){ self.name =名称 self.favoriteFood =最喜欢的食物 自我年龄=年龄 self.countryOfOrigin = countryOfOrigin } } 一点间距与此很长的路要走。 但是我认为我们可以做得更好。 每当我的代码看起来像这样的部分难以阅读时: … […]

如何在Swift中应用单一责任原则

有时,当我们学习编码时,很难理解类责任的概念。 这是因为我们的第一个项目变得难以维护,原因是我们的代码中按类包含许多行,更重要的是,它承担了许多责任。 真正了解类责任的一种好方法是考虑可伸缩性。 让我们看一个如何使用它的例子。 但是,在此之前,我想先介绍一些理论并解释为什么这个概念在软件设计中很重要。 每个类在软件项目中负有单个责任的思想,并且将责任封装在一个唯一的类中的想法有一个名称:单一责任原则 这是SOLID软件设计的5条主要原则之一,他们在面向对象编程中尝试定义一个指南,以拥有更易懂,灵活和可维护的软件。 这些原则是: 单一责任原则 开闭原则 里斯科夫替代原则 接口隔离原理 依赖倒置原则 这些概念的作者Robert C. Martin(他写了软件体系结构中最重要的书之一,即Clean Code)谈到“一类应该只有一个改变的理由” ,因此他将责任定义为更改。 例如,让我们考虑一个编译并打印报告的模块。 想象一下,可以出于两个原因而更改这种模块。 首先,报告的内容可能会改变。 其次,报告的格式可能会改变。 这两件事因不同的原因而改变。 一种实质性和一种化妆品。 单一责任原则说,问题的这两个方面实际上是两个单独的责任,因此应该放在单独的类或模块中。 耦合由于不同原因在不同时间发生变化的两件事,将是一个糟糕的设计。 使班级专注于单个关注点很重要的原因是,它使班级更加强大。 继续前面的示例,如果更改了报表编辑过程,则存在更大的危险,即如果打印代码属于同一类,则该打印代码将被破坏。 (示例摘自 Wikipedia ) 如果我们定义班级,知道他们在项目中的职责是什么,我们可以: 轻松了解其在代码各部分中的功能。 更快,更详细地修改现有逻辑。 以较少的问题查找错误或有害行为的来源。 不同类或模块中的抽象逻辑。 拆分时没有重大问题的实现,因此以后可以完全替换它们。 通过类或模块以更有效的方式定义单元测试,因此我们可以测试一小段代码,而无需测试更多的代码。 正如我之前说过的,您可以在类中考虑可扩展性以定义职责。 这就像思考是否可能在我们的项目中修改需求并在我们的体系结构中查看如何进行这些修改一样简单。 例如,如果我们看到要进行小的视图更改,则必须修改或修改业务逻辑,则我们没有正确定义项目中的职责。 让我们看一下Swift中的一个具体示例。 假设我有一个应用程序,它显示了商店中的商品列表。 到目前为止,我只有一个ItemsViewController负责该流的所有逻辑,数据和表示。 同样,当用户选择一个项目时,它也会打印日志。 您可以在https://github.com/fedejordan/SRPExample中查看代码 为此, ItemsViewController使用UITableView在列表中显示项目。 另外,我们使用一个名为ItemTableViewCell的UITableViewCell子类来显示这些元素。 但是,假设我们要更改视图,例如,使用UICollectionView. 在这种情况下会有什么问题? 视图代码与项目数据逻辑非常耦合。 如果我们更改视图,则很有可能也会修改负责该项目的类。 真正的问题在于以下几行: 让item […]

Swift解决方案:装饰器模式

Swift Solutions是涵盖设计模式的一系列文章。 在每篇文章中,我们讨论该模式是什么,何时应用以及如何以Swifty方式实现它。 在Swift解决方案的此迭代中,我们将在Swift中实现Decorator Pattern。 该模式提供了一种向应用程序添加功能的有用方法,并且是每个开发人员都可以在其套件中使用的出色工具。 我们将在方法上做到周密,以免材料发粘。 我们将在Swift中定义,说明和编码模式。 最终,您将了解该模式是什么,何时应用以及如何编码。 让我们直接进入定义! 限定 装饰器模式可动态修改核心对象的行为,而无需更改其现有的类定义。 装饰器模式有两个主要组成部分:一个将修改其行为的核心对象,以及一个在核心对象中带来更改的行为的装饰器对象。 这两个组件可以共同实现两个主要目标: 行为修改 …动态实现 行为修改 该模式的目的是在不修改其现有类实现的情况下更改核心对象的工作方式。 这是通过用装饰器包装核心对象来实现的,装饰器可以增强核心对象的默认行为。 装饰器从字面上完全“装饰”(修改)了它们包装的对象。 装饰器增强了核心对象中已经存在的行为。 它们与核心对象共享相同的接口,但提供不同的实现。 结果,客户端无法区分是使用装饰器还是使用核心对象:它们在两者上调用完全相同的函数/属性。 此共享接口允许装饰器充当中间人,拦截对核心对象的调用并为客户端提供自定义行为。 动态修改 装饰器不只是简单地修改行为,而是动态地修改行为。 换句话说,它们更改了对象在运行时(而不是编译时)的工作方式。 编译时行为修改很简单:只需要​​直接更改对象的类定义或子类并覆盖其默认实现即可。 但是,由于以下原因,可能不希望使用这些选项: 可以将核心对象类声明为final ,这可以防止子类化。 该类可以在第三方框架中定义,对它的任何更改都将在下一次框架更新时丢失。 可能存在许多所需的修改,并且可以将几种行为链接在一起以产生复杂的组合。 为每种可能的组合创建一个子类将导致类爆炸。 对现有类的更改可能会导致意外的副作用,从功能中断到崩溃。 当继承脆弱且未完全理解的旧版代码库时,尤其如此。 所有这些缺点使动态方法成为有吸引力的替代方法。 我们没有将具有不同行为的核心对象的多个变体硬编码,而是将每个行为分成了自己的专用包装器类。 然后,通常在用户输入期间,将这些专用包装器在运行时应用于核心对象。 用户选择他们的自定义项,然后我们应用包装器以产生所需的结果。 这是一种灵活的方法,因为行为的组合是通过根据需要增量应用包装器来实现的。 说明 我们将使用赛车游戏作为插图。 在此游戏中,玩家在每次比赛前选择车辆。 选择后,他们可以自定义车辆的轮胎,变速箱,发动机等。 这些自定义通过更改其统计信息来影响车辆的行驶方式。 在我们的示例中,统计信息将是速度和牵引力。 玩家可以选择更换零件以更好地适应他们的喜好。 例如,玩家可以选择将默认轮胎更改为越野轮胎,这些轮胎的抓地力更好,但速度却有所降低。 实行 让我们使用赛车插图来实现该模式。 protocol Transporting { func […]

警卫声明

掌握方向,清理代码,应用防护措施。 跟踪您的范围以及在Swift代码中可以访问哪些值和函数可能非常棘手,并且由于在Swift中大量使用了可选选项,处理这些可选情况可能会令人不知所措,并且您可能会迷失方向您的代码非常容易。 一种解决方案是使用保护性语句使代码更整洁,并在处理可选内容时避免缩进。 等等,什么是警卫声明? 保护性声明是一种迅速使代码执行检查的声明。 如果满足此检查条件,则代码将在范围内继续,但是如果不满足条件,则它将代码移出范围(正常失败)。 您可以为guard语句设置任何您喜欢的条件。 这里是: 一种看待这种情况的方法是,您有一扇门,如果人们不满足条件,门就会礼貌地把人们拒之门外。 为什么要使用它? 现在为什么要使用防护装置? 这不只是一个if语句吗? 为什么不使用if else语句检查nil或处理可选内容? 为什么要在词汇表中添加更多语法? 警卫声明会不会让初次编码的人感到不知所措? 好的,克里斯·华莱士,放松一下。 当然,这是您必须习惯的新语法,但是请放心,guard语句是将hullaballoo代码放入工具箱的好工具。 为什么? 原因有2个,它使您的代码更具可读性,并且实际上为您包装了可选内容以供您在代码中使用。 “解开可选项目? 为此,我可以使用if let语句,而无需学习新的语法。请参阅,过度使用if语句的可选绑定会导致Doom金字塔 。 这意味着代码被嵌入到if语句,缩进和方括号的多个级别中,这些语句最后类似于金字塔,并且(对于我个人而言)使您迷失在自己的代码中。 厄运金字塔 可选项的存在意味着swift拥有更多的安全检查和模板来安全地运行您的代码,这意味着更多的检查和方括号,并且您会多次陷入厄运金字塔。 使用保护性语句意味着您不会经常遇到这些语句,并且任何阅读您的代码的人都不会对代码某些部分的范围感到困惑,并且您的代码看起来像是一系列的门,而不是金字塔,非常清晰干净。 现在,对我来说,这只是巩固了一点,后卫的声明真是棒极了 。 好的,让我们尝试使用警卫语句来分析字典,可选和nil的雷区,如果您不相应地构建代码,它们会很快造成混乱。 这是我们编写的第一个代码,不使用保护语句,也不使用if let可选绑定来获取数据。 你现在在树林里。 它会变得非常混乱,非常快。 现在,让我们尝试使用Guards语句。 啊……那更好。 干净,就像一系列的大门一样,有趣阅读。 结论:警卫使您的代码看起来更好,更清晰,而学习新语法的代价却很小。 现在……警惕!

无主与弱

弱和无所有权用于解决内存泄漏和保留周期。两者都不会增加保留计数。 话虽如此,我们不会像弱者那样使用无人值守 。 因为,我会说要么我们不想弄乱功能,要么我们不想三思而后行,就像其他人一样,我确实拥有甚至没有单个无主变量的源代码。 最近,我有机会更多地了解这些术语并进行猜测,只要您知道何时以及如何放置它们,我发现的弱点和无名者都非常简单易用。因此,在这里,我将帮助您找到何时以及如何 🙂 无人 要使用无主属性时应记住的重要点 ·当另一个实例具有相同的生存时间或更长的生存时间时,可以使用无所有权。 以客户和信用卡为例。 在这里,没有客户就不能存在信用卡。 信用卡实例永远不会超过它所引用的客户,并且我们仅通过在初始化程序中传递客户实例来创建信用卡实例。 因此,我们可以绝对保证没有客户就不会存在此信用卡。 当客户为零时,客户和信用卡都将被释放。 当在闭包中使用时,未拥有的引用的行为就像它们是隐式解开的optionals.so,因此当您在闭包中使用它们时,您不必进行可选的链接,也不必解开它们。 弱 每当引用被释放时,weak都会使指针无效,但无主不会这样做,因此可能导致指针悬空。 因为弱引用需要在运行时允许将其值更改为nil ,所以它们始终被声明为变量和可选 当在闭包中使用弱引用时,它们的作用类似于optionals.so,因此在闭包中使用弱引用时,必须对其进行可选的链接。 您是否想过闭包中[弱我]或[无主我]是什么意思。 它被称为CaptureList 好吧,方括号表示捕获列表,它只是一个数组 ,其中弱的self是其中的唯一对象。 作为一个数组,捕获列表不仅仅可以捕获弱的自我。 您可以捕获任意多的内容: 我希望这是有用的,如果您有任何疑问或需要澄清,我很想听听他们的意见!

Swift解决方案:Flyweight模式

Swift Solutions是涵盖设计模式的一系列文章。 在每篇文章中,我们讨论该模式是什么,何时应用以及如何以Swifty方式实现它。 飞量模式是一种节省内存的模式,当有许多要实例化的对象共享相似性时使用。 在本文中,我们将说明并编写Swift中的flyweight模式。 阅读本文之后,您将知道在必须创建大量相关对象时如何节省内存。 插图 首先,让我们使用文本编辑器的经典示例。 文本编辑器实例化并重复使用所有26个字母。 例如,当键入“ HELLO WORLD”时,我们将在三个不同的时间重新创建“ L”字符。 这很浪费,因为我们创建了三个字符对象来表示相同的字母。 flyweight模式的目标是共享可重用的对象,而不是不必要地复制它们,从而使我们的文本编辑器轻巧。 我们通过首先将对象分为两部分来重用对象:外部状态和内部状态。 外在的是指对象的根据上下文而改变的部分,因此无法共享。 例如,一个字符可能被加粗,着色或具有较大的字体大小。 这类数据不可重用,因为我们不希望给定字符的所有实例都共享这些属性。 另一方面,固有数据表示跨字符保持相同的内容。 固有数据的一个示例是给定字符的形状。 所有重复的字符都是渲染的形状,并且该形状不会从一次出现更改为下一次出现。 每当角色出现在我们的整个作品中时,我们都可以使用相同的“ L”形,然后对其应用外在属性。 总结一下: 内部数据是不可变的,相同的,没有上下文的,因此可以重用。 外在数据是可变的和上下文的,因此,并非在所有情况下都可以重用。 通过分离内部数据和外部数据,我们能够确定我们可以在对象中重用的内容。 考虑到这一点,让我们跳入代码示例。 实作 我们将在我们的代码中模拟一支装满步兵的军队。 我们还可以有弓箭手,将军和许多其他类型的士兵,并且我们想尽可能地重复使用,因为这些士兵实体中的每一个都会大量存在。 protocol Soldier { func render(from location: CGPoint, to newLocation: CGPoint) } 首先,我们创建一个Soldier协议,该协议具有将士兵在网格上的原始位置以及士兵将要移动到的新位置的功能。 我们代码的目标是随着战斗的进行在网格上绘制步兵部队。 由于每个士兵都有一个唯一的位置,因此位置被认为是外部状态。 Flyweight对象不会存储位置,但是它们仍需要通过其功能输入来处理外部数据。 不久之后会更多。 飞行重量 让我们看看Flyweight对象的外观: class Infantry: Soldier { private […]