这篇对新手友好的文章涵盖了您在职业生涯初期应该熟悉的另一轮气味和重构。 我们介绍了案例陈述,多态性,空对象和数据类。

主题

  • 案例陈述
  • 多态性
  • 空对象
  • 资料类别

案例陈述

这也可以被称为“清单气味”之类的东西。 案例陈述之所以散发出气味,是因为它们会引起重复-它们通常也很不雅致。 它们还可能导致不必要的大类,因为所有这些对各种(并可能不断增长的)案例场景做出响应的方法通常最终都归于同一类中,从而承担了各种混合的责任。 您有很多私有方法在自己的类中会更好,这种情况并非罕见。

如果要扩展case语句,则会出现一个大问题。 然后,您必须一次又一次地更改该特定方法。 不仅在这里,因为经常有双胞胎在整个地方重复,现在也需要更新。 确定繁殖错误的好方法。 您可能还记得,我们希望对扩展开放,但对修改不开放。 在这里,修改是不可避免的,只是时间问题。

您使自己更难以提取和重用代码,而且它是一阵滴答作响的炸弹。 视图代码通常取决于这种情况陈述,这些陈述随后会复制气味并为将来的shot弹枪外科手术大开大门。 哎哟! 同样,在找到正确的执行方法之前询问对象是对“ 告诉-不要询问 ”原则的严重违反。

多态性

有一种很好的技术可以处理对case语句的需求。 花哨的单词来了! 多态性 。 这使您可以为不同的对象创建相同的接口,并使用不同方案所需的任何对象。 您只需交换适当的对象,它就可以满足您的需求,因为它具有相同的方法。 它们在这些方法下的行为是不同的,但是只要对象响应相同的接口,Ruby就不会在意。 例如, VegetarianDish.new.orderVeganDish.new.order行为不同,但是对#order的响应方式相同。 您只想订购,不回答很多问题,例如是否吃鸡蛋。

通过为case语句分支提取一个类并将该逻辑移到该类的新方法中来实现多态。 您将继续对条件树中的每个分支执行此操作,并为它们提供相同的方法名称。 这样,您可以将该行为封装到最适合做出此类决定且没有理由进行进一步更改的对象中。 瞧,这样您就可以避免在一个对象上遇到所有这些烦人的问题,只需告诉它应该做什么。 当需要更多条件情况时,您只需创建另一个类即可使用相同的方法名称来处理该单一职责。

案例陈述逻辑

class Operationdef pricecase @mission_tpewhen :counter_intelligence@standard_fee + @counter_intelligence_feewhen :terrorism@standard_fee + @terrorism_feewhen :revenge@standard_fee + @revenge_feewhen :extortion@standard_fee + @extortion_feeendend
endcounter_intel_op = Operation.new(mission_type: :counter_intelligence)
counter_intel_op.priceterror_op = Operation.new(mission_type: :terrorism)
terror_op.pricerevenge_op = Operation.new(mission_type: :revenge)
revenge_op.priceextortion_op = Operation.new(mission_type: :extortion)
extortion_op.price

在我们的示例中,我们有一个Operation类,该类需要先询问其mission_type然后才能告诉您其价格。 很容易看出,这种price方法只是在添加一种新的操作后才等待更改。 当您还希望在视图中显示该更改时,也需要在此应用该更改。 (仅供参考,对于视图,可以在Rails中使用多态部分函数,​​以避免这些case语句在整个视图中爆炸。)

多态类

class CounterIntelligenceOperationdef price @standard_fee + @counter_intelligence_feeend
endclass TerrorismOperationdef price @standard_fee + @terrorism_feeend
endclass RevengeOperationdef price@standard_fee + @revenge_feeend
endclass ExtortionOperationdef price@standard_fee + @extortion_feeend
endcounter_intel_op = CounterIntelligenceOperation.new
counter_intel_op.priceterror_op = CounterIntelligenceOperation.new
terror_op.pricerevenge_op = CounterIntelligenceOperation.new
revenge_op.priceextortion_op = CounterIntelligenceOperation.new
extortion_op.price

因此,我们没有遇到麻烦,而是创建了一堆操作类,它们自己知道自己的费用加起来等于最终价格。 我们可以告诉他们给我们价格。 您不必总是摆脱原始的( Operation )类,仅当您发现自己已经提取了所有知识时才可以。

我认为案例陈述背后的逻辑是不可避免的。 在找到能够完成任务的对象或行为之前必须经过某种检查清单的情况太常见了。 问题只是如何最好地处理它们。 我赞成不要重复它们,而是使用面向对象编程的工具来设计离散类,这些类可以通过它们的接口轻松换出。

空对象

到处检查零是一种特殊的案例陈述气味。 向对象询问nil通常是一种隐藏的案例陈述。 有条件地处理nil可以采用object.nil?的形式object.nil?object.present?object.try ,然后在您的聚会上出现nil的情况下采取某种措施。

另一个比较狡猾的问题是询问一个对象的真实性(是否存在或是否无效),然后采取一些措施。 看起来无害,但这只是伪装。 不要上当:三元运算符或|| 当然,运营商也属于这一类。 换句话说,条件语句不仅可以清楚地标识为, unlessif-elsecase语句。 他们有更巧妙的方法来破坏您的聚会。 不要问对象是否为零,而要告诉他们是否缺少您想要的对象,因为空对象现在负责响应您的消息。

空对象是普通类。 它们没有什么特别的,只是一个“花哨的名字”。 您提取一些与nil相关的条件逻辑,然后对其进行多态处理。 您将包含这些行为,通过这些类控制应用程序的流,并且还具有可用于其他适合它们的扩展的对象。 考虑一下TrialNullSubscription )类如何随着时间增长。 它不仅更具DRY和yadda-yadda-yadda功能,而且更具描述性和稳定性。

使用空对象的一大好处是事情不会那么容易崩溃。 这些对象响应的消息与模拟对象的消息相同(当然,您不必总是将整个API复制到空对象中),这使您的应用没有理由发疯。 但是,请注意,空对象封装了条件逻辑,而没有完全删除它。 您只是为此找到了一个更好的家。

由于在您的应用程序中执行很多与nil相关的操作,对您的应用程序具有很强的传染性和不良影响,因此我想将空对象视为“ Nil Containment Pattern”(请不要起诉我!)。 为什么会传染? 因为如果你通过nil周围,别的地方在你的层次结构,另一种方法是早晚也不得不问,如果nil是在城里,然后导致另一轮采取反措施来应对这种情况。

换句话说,nil不好玩,因为要求其存在会传染。 询问对象的nil极有可能总是差设计,没有进攻的症状,不要心疼!-we've都在那里。 我不想对“零友善”有多不友善,但愿提及一些事情:

  • 尼尔(Nil)是一名聚会警察(抱歉,尼尔(nil),不得不说)。
  • Nil没有帮助,因为它没有意义。
  • 尼尔对任何事情都没有回应,并且违反了“ 鸭打字 ”的想法。
  • 零错误消息通常很难处理。
  • 尼尔迟早会咬你。

总体而言,对象丢失某些东西的情况非常频繁地出现。 一个经常被引用的例子是一个具有注册UserNilUser的应用程序。 但是,如果不存在的用户清楚地浏览了您的应用程序,那么这个用户是一个愚蠢的概念,因此拥有尚未注册的Guest可能会更酷。 缺少的订阅可能是Trial ,零费用可能是Freebie ,依此类推。

为空对象命名有时是显而易见的,而且很容易,有时会非常困难。 但是,请尽量避免使用前导“ Null”或“ No”来命名所有空对象。 你可以做得更好! 我认为,提供一些背景信息将大有帮助。 选择一个更具体和有意义的名称,以反映实际用例。 这样,您就可以与其他团队成员以及未来的自我更清晰地沟通。

有两种方法可以实现此技术。 我将向您展示一个我认为直接且对初学者友好的视图。 我希望这是了解更多涉及方法的良好基础。 当您反复遇到某种涉及nil的条件时,您就会知道该是简单地创建一个新类并将该行为移到其他地方的时候了。 之后,您让原始班级知道这个新玩家现在什么都没有了。

在下面的示例中,您可以看到Spectre类对nil要求过多,并不必要地使代码混乱。 它希望确保在决定收费之前我们有一个evil_operation 。 您可以看到违反“告诉不要询问”的行为吗?

另一个有问题的部分是为什么Spectre需要关心零价格的实施。 try方法还通过or|| )语句evil_operation询问evil_operation是否具有处理虚无的priceevil_operation.present? 确实犯了同样的错误。 我们可以简化一下:

class Spectreinclude ActiveModel::Modelattr_accessor :credit_card, :evil_operationdef chargeunless evil_operation.nil?evil_operation.charge(credit_card)endenddef has_discount?evil_operation.present? && evil_operation.has_discount?enddef priceevil_operation.try(:price) || 0end
endclass EvilOperationinclude ActiveModel::Modelattr_accessor :discount, :pricedef has_discount?discount  enddef charge(credit_card)credit_card.charge(price)end
end
class NoOperationdef charge(creditcard)"No evil operation registered"enddef has_discount?falseenddef price0end
endclass Spectreinclude ActiveModel::Modelattr_accessor :credit_card, :evil_operationdef chargeevil_operation.charge(credit_card)enddef has_discount?evil_operation.has_discount?enddef priceevil_operation.priceendprivatedef evil_operation @evil_operation || NoOperation.newend
endclass EvilOperationinclude ActiveModel::Modelattr_accessor :discount, :pricedef has_discount?discountenddef charge(credit_card)credit_card.charge(price)end
end

我想这个例子很简单,可以立即看到空对象有多优雅。 在我们的例子中,我们有一个NoOperation空类,该类知道如何通过以下方式处理不存在的邪恶操作:

  • 处理金额为0费用。
  • 知道不存在的操作也没有折扣。
  • 给卡充电时返回一些提示性的错误字符串。

我们创建了一个处理虚无的新类,一个处理无东西的对象,如果显示nil ,则将其交换到旧类中。 API是此处的关键,因为如果它与原始类的API匹配,则可以无缝交换具有我们需要的返回值的null对象。 这正是我们在私有方法Spectre#evil_operation 。 如果我们有evil_operation对象,则需要使用该对象; 如果没有,我们将使用知道如何处理这些消息的nil变色龙,因此evil_operation将不再返回nil。 鸭子最好地打字。

现在,我们有问题的条件逻辑被包装在一个地方,而空对象负责Spectre正在寻找的行为。 干燥! 我们有点从nil无法处理的原始对象重建了相同的接口。 请记住,nil在接收消息方面做得很差-永远没人在家! 从现在开始,它只是告诉对象该怎么做,而无需先询问他们的“权限”。 同样很酷的是,根本不需要接触EvilOperation类。

最后但并非最不重要的一点是,我摆脱了检查是否在Spectre#has_discount?存在恶意操作Spectre#has_discount? 。 无需确保存在一项操作即可获得折扣。 由于存在null对象,因此Spectre类更苗条,并且不承担其他类的责任。

一个好的指导原则是不要检查对象是否愿意做某事。 像钻探中士一样命令他们。 通常情况下,它在现实生活中可能并不酷,但这对于面向对象的编程是一个很好的建议。 现在给我20!

通常,使用多态而不是case语句的所有好处也适用于Null对象。 毕竟。 这只是case语句的特例。 缺点也一样:

  • 了解实现和行为可能会变得很棘手,因为代码会四处传播,并且空对象的存在并不十分明确。
  • 添加新行为可能需要在空对象及其对应对象之间保持同步。

资料类别

让我们以一些轻松的内容结束本文。 这是一种气味,因为它是一个除了获取和设置其数据外没有其他行为的类-基本上,它不过是一个没有额外方法处理该数据的数据容器。 这使它们本质上是被动的,因为此数据保留在那里供其他类使用。 我们已经知道这不是理想的情况,因为它使人羡慕不已 。 一般而言,我们希望具有状态的类(即它们要处理的数据)以及通过可以对该数据进行操作而不会造成太大阻碍或窥探其他类的方法的行为。

您可以通过将可能对数据起作用的其他类的行为提取到数据类中来开始重构此类。 这样,您可能会慢慢吸引该数据的有用行为并为其提供适当的位置。 如果这些类随时间增长行为,那完全没问题。 有时您可以轻松地移走整个方法,有时您需要先提取较大方法的一部分,然后将其提取到数据类中。 如有疑问,如果可以减少其他类对数据类的访问,则绝对应该这样做,并将这种行为移到另一端。 您的目标应该是在某个时候退休,使其他对象可以访问该数据的getter和setter方法。 结果,您将在各个类之间实现较低的耦合-这始终是一个胜利!

总结思想

瞧,这并没有那么复杂! 关于代码气味,有很多花哨的术语和复杂的发音技术,但是希望您意识到,气味及其重构也具有数量有限的几个特征。 代码的气味永远不会消失或变得无关紧要-至少要等到AI被增强我们的身体时,这些AI才能让我们编写距现在还差几年的质量代码。

在过去的三篇文章中,我向您介绍了在您面向对象的编程生涯中遇到的最重要和最常见的代码嗅觉场景中的很大一部分。 我认为这对新手来说是一个很好的介绍,并且希望代码示例足够详细,以方便地跟随线程。

一旦弄清了设计质量代码的这些原则,您将立即领会新的气味及其重构。 如果到目前为止,您仍然感觉自己并没有放松大局,那么我认为您已经准备好接近老板级别的OOP状态。

翻译自: https://code.tutsplus.com/articles/rubyrails-code-smell-basics-03--cms-25441

Ruby / Rails代码气味基础03相关推荐

  1. 视频教程-Ruby on Rails打造企业级RESTful API项目实战我的云音乐-Ruby/Rails

    Ruby on Rails打造企业级RESTful API项目实战我的云音乐 任苹蜻,爱学啊创始人 & CEO,曾就职于某二车手公司担任Android工程师后离职创办爱学啊,我们的宗旨是:人生 ...

  2. 【重难点】【Java基础 03】hashCode() 和 equals()、代理模式

    [重难点][Java基础 03]重写hashCode() 和equals(). 文章目录 [重难点][Java基础 03]重写hashCode() 和equals(). 一.hashCode() 和 ...

  3. 推荐系统基础03:矩阵分解与FM

    推荐系统基础03:矩阵分解与FM 1. 隐语义模型与矩阵分解 2. 隐语义模型(LFM) 隐语义 隐特征矩阵 3.矩阵分解模型 4. 矩阵分解算法的求解 Basic SVD FunkSVD(又称RSV ...

  4. 滤波器基础03——Sallen-Key滤波器、多反馈滤波器与Bainter陷波器

    滤波器基础系列博客,传送门: 滤波器基础01--滤波器的种类与特性 滤波器基础02--滤波器的传递函数与性能参数 滤波器基础03--Sallen-Key滤波器.多反馈滤波器与Bainter陷波器 滤波 ...

  5. Ruby/Rails学习教程-Hello Ruby

    项目需要,我的Ruby之旅也开始了.学习之初必定是各种菜各种困惑,但是作为Developer,学习新知识是我们的基本技能.于是决定写点什么,来自我记录自己的成长轨迹. 从最初的Hello World ...

  6. 【Taichi】代码框架基础、数据与计算核

    目录:[Taichi]代码框架基础.数据与计算核 Taichi 代码框架基础 引入包与初始化 作用域 Taichi 支持的数据类型 基元数据类型 修改默认数据类型 数据类型转换 复合数据类型 场数据类 ...

  7. python基础代码事例-推公式到写代码-python基础

    推公式到写代码-python基础 希望你能像看小说看杂文一样的心情看完这一系列,因为学习不总是枯燥的,希望像聊天一样娓娓道来. 专辑系列的阅读对象是那些懂些高等数学和线性代数,但没有经过编码训练的人. ...

  8. Ruby/Rails 生态环境、社区、资料 Ecosystem

    Ruby/Rails 生態圈 Ecosystem 一個成功的開放原始碼程式語言和框架,背後一定有一個強大的社群在支持.團隊和個人的時間成本有限,你不可能每個用到的工具和函式庫工具都自己從頭開發.因此, ...

  9. 1.使用适配器模式设计一个仿生机器人:要求机器人可以模拟各种动物行为,在机器人中定义了一系列方法,如机器人发声方法talk(),机器人移动方法move()等。如果希望在不改变已有Bird类代码的基础上

    1.使用适配器模式设计一个仿生机器人:要求机器人可以模拟各种动物行为,在机器人中定义了一系列方法,如机器人发声方法talk(),机器人移动方法move()等.如果希望在不改变已有Bird类代码的基础上 ...

最新文章

  1. mongodb java项目 源码_spring项目整合mongodb进行开发
  2. 春节将至 香港推广“绿色年宵”呼吁惜物减废
  3. [云炬创业基础笔记]第二章创业者测试3
  4. 台湾大学林轩田机器学习基石课程学习笔记7 -- The VC Dimension
  5. P4770-[NOI2018]你的名字【SAM,线段树合并】
  6. 基于JavaFX的Linux进程树
  7. undolog 是binlog_mysql日志redo log、undo log、binlog以及作用看这篇就可以啦
  8. 95-872-058-源码-CEP-CEP规则匹配
  9. 20个很有帮助的 Web 前端开发教程
  10. ios开发之--令UITableView滚动到指定位置
  11. Eclipse安装SVN
  12. android html文字加中间横线_HTML 语法简要总结
  13. CF1399D Binary String To Subsequences
  14. Windows系统查询硬盘序列号
  15. 腾讯程序员平均月薪7.48万,分分钟变身“柠檬精”
  16. 基于 Django 使用 qrcode 模块生成二维码
  17. 什么是绿色工厂?申报绿色工厂对企业有什么好处?
  18. 面向对象类与对象的关系
  19. 简单解决高分屏模糊问题
  20. Hudson插件开发入门体验

热门文章

  1. VC++根据cpu和磁盘序列号生成注册码(附源码)
  2. 快速搞懂web2.0
  3. 基于树莓派采集网关6 三菱数控机床 CNC 信息采集
  4. python有效边界_Markowitz有效边界和投资组合优化基于Python(附代码)
  5. PicoScope 7 汽车软件教程
  6. 机器学习、监督学习、非监督学习、强化学习、深度学习、迁移学习
  7. c语言数组求素数,C语言入门:一维数组求素数
  8. 功能核磁共振影像分析AFNI教程(3)
  9. 从《零钱兑换》开始的《背包问题》
  10. 怎么传文件到服务器上,怎样传文件到服务器上