用 Python 重构 God 类:万物皆有其位


抵制将更多代码转储到您正在处理的类中而不是打开正确的代码或者创建一个新代码的冲动是困难的。 你为什么不把那个盘子放在洗碗机而不是水槽里呢?

神的课就是从这里来的。 班级知道的太多了。 带有导入列表的那个永远不会结束。 万物之王,万物之主。

我将让 Eric S. Raymond 在《Unix 编程的艺术》中总结一下为什么在模块化规则下这是不可取的:

编写不至于一头雾水的复杂软件的唯一方法是降低其全局复杂性——用定义明确的接口连接的简单部分构建它,这样大多数问题都是局部的,你可以有一些希望 在不破坏整体的情况下升级部分。

我们希望在每个类中通过做好一件事来回到 SOLID 的单一职责原则。 好消息是您可以随时开始一点一点地清理它。

虽然这种反模式的某些方面可以在项目的任何地方蔓延,但某些关键字(如“通用”和“实用程序”)引起了我的注意,因为它们是垃圾场。

让我们从一个受我从事的项目中的代码启发的示例开始。 如果不停止,这里有一个小版本,可以很容易地增长到数千行不相关的行:

class CommonUtils:
  def send_alert(self, message):
    try:
      return client.chat_postMessage(
        channel="C01ABCD1234",
        text=f"BE CAREFUL! {message}"
      )
    except Exception as e:
      print(f"Error: {e}")

  def get_service_name_from_url(self, url):
    return url.split('.')[1]

  def read_file(self, path, filename):
    with open(path + "/" + filename) as f:
      return f.read()

最好的意图就在这里。 通过聊天客户端包装警报是有意义的,因此如果我们需要或想要更改为另一项服务,我们可以在一个地方进行更改。

但是,这有多大可能被埋在这个无法描述的类中间而被下一个人直接使用客户端,或者更糟的是在这个类的第 67 行下面添加另一个几乎但不完全相同的功能? 只需付出更多的努力,我们就可以将这个函数移动到它自己的 message_service.py 中

class MessageServiceException:
  pass

class MessageService:
  def alert(self, message):
    try:
      client.chat_postMessage(
        channel="C01ABCD1234",
        text=f"BE CAREFUL! {message}"
      )
    except Exception as e:
      raise(MessageServiceException(e))

添加一个很好的单元测试,这更有可能看到重用和扩展。

接下来,那个方便的小解析函数似乎很适合我们的普通类,对吧? 然而,我们的真实情况是一个具有特定格式的 URL 作为纯字符串在应用程序中传递,当我们需要它的时候,我们调用这个函数。

现在,我把那个小东西放在哪里了? 它在我和所有那些小家具一起得到的一堆单一内六角扳手中。 这是领域对象的一个很好的候选者:

def getServiceNameFromUrl(self, url):
  return url.split(‘.’)[1]

变成:

class ServiceUrl:
  def __init__(self, urlstring):
    parsedUrl = urllib.parse(str(urlstring).strip())
    self.host = parsedUrl.netloc
    host_parts = self.host.split(‘.’)
    self.app_name = host[0]
    self.service_name = host[1]
    self.region = host[2]
    self.domain = host[3:].join(‘.’)
    self.path = parseUrl.path

  def __str__(self):
    return f"https://{self.host}/{self.path}"

使用它很简单:

ServiceUrl("https://app.service.region.example.com/root").service_name


列表中的最后一个重构是文件读取的包装器:

def read_file(self, path, filename):
  with open(path + “/” + filename) as f:
    return f.read()

到目前为止,这是三个中最简单的一个。 删除它! 包装基本功能有一些不可抗拒的东西,但它是有害的。 它包装的函数的原始 Python 语法与包装器一样具有可读性。

总是有这样的想法,让这个函数在以后进行更改时会有用,但如果你真的在应用程序中需要读取文件的任何地方都使用了这个函数,你就永远不敢更改它。 破坏某些东西的可能性太大了。 你会在每个项目中看到这种类型的代码,我鼓励你毫不后悔地取消它。

每当您看到一个您无法用一句话描述它的用途或无法从名称中猜出它的作用的类时,请采用我们在本文中讨论过的这些策略,并不断分解,直到它成为记忆中的一部分。 Git rm,git commit,git push。

这会让你震惊,这从未发生过。

展开阅读全文

页面更新:2024-04-10

标签:万物   候选者   应用程序   函数   客户端   东西   代码   文件   地方   项目

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top