如何将 SOLID 原则应用到现有项目中
我对这个问题的主观性表示歉意,但我有点卡住了,我希望以前处理过这个问题的人能提供一些指导和建议:
我有(现在已经成为)一个用 C# 2.0 编写的非常大的 RESTful API 项目我的一些课程已经变得很可怕。 我的主要 API 类就是一个这样的例子——有几十个成员和方法(可能接近数百个)。 正如您可以想象的那样,这正在成为一个小噩梦,不仅是维护此代码,甚至只是导航代码也变成了一件苦差事。
我对 SOLID 原则相当陌生,并且非常喜欢设计模式(但我仍处于可以实现它们的阶段,但还不足以知道何时使用 他们 - 在不太明显的情况下)。
我需要缩小班级规模,但我不知道如何最好地做到这一点。 我的 StackOverflow 同事能否建议他们如何采用现有的代码单体并将其缩小规模?
I apologize for the subjectiveness of this question, but I am a little stuck and I would appreciate some guidance and advice from anyone who's had to deal with this issue before:
I have (what's become) a very large RESTful API project written in C# 2.0 and some of my classes have become monstrous. My main API class is an example of this -- with several dozen members and methods (probably approaching hundreds). As you can imagine, it's becoming a small nightmare, not only to maintain this code but even just navigating the code has become a chore.
I am reasonably new to the SOLID principles, and I am massive fan of design patterns (but I am still at that stage where I can implement them, but not quite enough to know when to use them - in situations where its not so obvious).
I need to break my classes down in size, but I am at a loss of how best to go about doing it. Can my fellow StackOverflow'ers please suggest ways that they have taken existing code monoliths and cut them down to size?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
单一职责原则 - 一个类应该只有一个改变的理由。 如果您有一个单一的类,那么它可能有多个理由需要更改。 只需定义您要改变的一个原因,并尽可能合理地细化。 我建议从“大”开始。 将三分之一的代码重构到另一个类中。 一旦你有了这些,就可以重新开始你的新课程。 从一级直接升到20级,太令人畏惧了。
开放/封闭原则 - 类应该对扩展开放,但对更改封闭。 在合理的情况下,将您的成员和方法标记为虚拟或抽象。 每个项目本质上应该相对较小,并为您提供一些基本功能或行为定义。 但是,如果您稍后需要更改功能,您将能够添加代码,而不是更改代码来引入新的/不同的功能。
里氏替换原则 - 一个类应该可以替换它的基类。 在我看来,这里的关键是正确地继承。 如果您有一个巨大的 case 语句,或者两页 if 语句来检查对象的派生类型,那么您就违反了这一原则,需要重新考虑您的方法。
接口隔离原则 - 在我看来,该原则与单一职责原则非常相似。 它仅适用于高级(或成熟)类/接口。 在大型类中使用此原则的一种方法是让您的类实现一个空接口。 接下来,将使用您的类的所有类型更改为接口的类型。 这会破坏你的代码。 然而,它会准确地指出你是如何上课的。 如果您有三个实例,每个实例都使用自己的方法和属性子集,那么您现在知道需要三个不同的接口。 每个界面都代表一组功能的集合,以及一个需要更改的原因。
依赖倒置原则 - 父/子寓言让我明白了这一点。 想想一个父类。 它定义了行为,但不关心肮脏的细节。 这是可靠的。 然而,子类都是关于细节的,不能依赖它,因为它经常变化。 你总是想依赖父母、负责任的班级,而不是相反。 如果您有一个依赖于子类的父类,那么当您更改某些内容时,您会得到意想不到的行为。 在我看来,这与 SOA 的思维方式是一样的。 服务契约定义了输入、输出和行为,但没有细节。
当然,我的观点和理解可能不完整或错误。 我建议向掌握这些原则的人学习,比如鲍勃叔叔。 对我来说,他的书是一个很好的起点,C# 中的敏捷原则、模式和实践。 另一个很好的资源是Hansel 分钟的鲍勃叔叔。
当然,正如 Joel 和 Jeff 指出的,这些是原则,而不是规则。 它们是帮助指导你的工具,而不是国家的法律。
编辑:
我刚刚发现这些SOLID screencasts看起来十分有趣。 每一篇大约长 10-15 分钟。
Single Responsibility Principle - A class should have only one reason to change. If you have a monolithic class, then it probably has more than one reason to change. Simply define your one reason to change, and be as granular as reasonable. I would suggest to start "large". Refactor one third of the code out into another class. Once you have that, then start over with your new class. Going straight from one class to 20 is too daunting.
Open/Closed Principle - A class should be open for extension, but closed for change. Where reasonable, mark your members and methods as virtual or abstract. Each item should be relatively small in nature, and give you some base functionality or definition of behavior. However, if you need to change the functionality later, you will be able to add code, rather than change code to introduce new/different functionality.
Liskov Substitution Principle - A class should be substitutable for its base class. The key here, in my opinion, is do to inheritance correctly. If you have a huge case statement, or two pages of if statements that check the derived type of the object, then your violating this principle and need to rethink your approach.
Interface Segregation Principle - In my mind, this principle closely resembles the Single Responsibility principle. It just applies specifically to a high level (or mature) class/interface. One way to use this principle in a large class is to make your class implement an empty interface. Next, change all of the types that use your class to be the type of the interface. This will break your code. However, it will point out exactly how you are consuming your class. If you have three instances that each use their own subset of methods and properties, then you now know that you need three different interfaces. Each interface represents a collective set of functionality, and one reason to change.
Dependency Inversion Principle - The parent / child allegory made me understand this. Think of a parent class. It defines behavior, but isn't concerned with the dirty details. It's dependable. A child class, however, is all about the details, and can't be depended upon because it changes often. You always want to depend upon the parent, responsible classes, and never the other way around. If you have a parent class depending upon a child class, you'll get unexpected behavior when you change something. In my mind, this is the same mindset of SOA. A service contract defines inputs, outputs, and behavior, with no details.
Of course, my opinions and understandings may be incomplete or wrong. I would suggest learning from people who have mastered these principles, like Uncle Bob. A good starting point for me was his book, Agile Principles, Patterns, and Practices in C#. Another good resource was Uncle Bob on Hanselminutes.
Of course, as Joel and Jeff pointed out, these are principles, not rules. They are to be tools to help guide you, not the law of the land.
EDIT:
I just found these SOLID screencasts which look really interesting. Each one is approximately 10-15 minutes long.
Martin Fowler 有一本经典书籍 - 重构:改进现有代码的设计。
在那里,他提供了一套设计技术和决策示例,使您现有的代码库更易于管理和维护(这也是 SOLID 原则的全部内容)。 尽管重构有一些标准例程,但它是一个非常自定义的过程,一种解决方案无法应用于所有项目。
单元测试是此过程成功的支柱之一。 您确实需要用足够的代码覆盖率来覆盖现有的代码库,以便确保在更改时不会破坏内容。 实际上,使用具有模拟支持的现代单元测试框架将鼓励您更好的设计。
有像 ReSharper(我最喜欢的)和 CodeRush 这样的工具可以帮助完成繁琐的代码更改。 但这些通常都是琐碎的机械事情,做出设计决策是更加复杂的过程,并且没有那么多的工具支持。 使用类图和 UML 会有所帮助。 实际上,这就是我要开始的。 尝试理解已有的内容并为其添加一些结构。 然后,您可以从那里决定不同组件之间的分解和关系,并相应地更改代码。
希望这有帮助,重构愉快!
There's a classic book by Martin Fowler - Refactoring: Improving the Design of Existing Code.
There he provides a set of design techniques and example of decisions to make your existing codebase more manageable and maintainable (and that what SOLID principals are all about). Even though there are some standard routines in refactoring it is a very custom process and one solution couldn't be applied to all project.
Unit testing is one of the corner pillars for this process to succeed. You do need to cover your existing codebase with enough code coverage so that you'd be sure you don't break stuff while changing it. Actually using modern unit testing framework with mocking support will lead encourage you to better design.
There are tools like ReSharper (my favorite) and CodeRush to assist with tedious code changes. But those are usually trivial mechanical stuff, making design decisions is much more complex process and there's no so much tool support. Using class diagrams and UML helps. That what I would start from, actually. Try to make sense of what is already there and bring some structure to it. Then from there you can make decisions about decomposition and relations between different components and change your code accordingly.
Hope this helps and happy refactoring!
这将是一个耗时的过程。 您需要阅读代码并识别不符合 SOLID 原则的部分并重构为新的类。 使用像 Resharper (http://www.jetbrains.com) 这样的 VS 插件将有助于重构过程。
理想情况下,您将拥有良好的自动化单元测试覆盖率,以便确保您的更改不会给代码带来问题。
更多信息
在主 API 类中,您需要识别彼此相关的方法,并创建一个更具体地表示该方法执行的操作的类。
例如,
假设我有一个 Address 类,其中包含包含街道号码、名称等的单独变量。该类负责插入、更新、删除等。如果我还需要以邮政地址的特定方式格式化地址,我可以有一个名为 GetFormattedPostalAddress() 的方法,它返回格式化的地址。
或者,我可以将此方法重构为一个名为 AddressFormatter 的类,该类在其构造函数中接受一个 Address,并具有一个名为 PostalAddress 的 Get 属性,该属性返回格式化的地址。
这个想法是将不同的职责分成不同的类。
It will be a time consuming process. You need to read the code and identify parts that do not meet the SOLID principles and refactor into new classes. Using a VS add-in like Resharper (http://www.jetbrains.com) will assist with the refactoring process.
Ideally you will have good coverage of automated unit tests so that you can ensure your changes do not introduce problems with the code.
More Information
In the main API class, you need to identify methods that relate to each other and create a class that more specifically represents what actions the method performs.
e.g.
Let's say I had an Address class with separate variables containing street number, name, etc. This class is responsible for inserting, updating, deleting, etc. If I also needed to format an address a specific way for a postal address, I could have a method called GetFormattedPostalAddress() that returned the formatted address.
Alternatively, I could refactor this method into a class called AddressFormatter that takes an Address in it constructor and has a Get property called PostalAddress that returns the formatted address.
The idea is to separate different responsibilities into separate classes.
当遇到这种类型的事情时,我所做的就是查看现有的代码库(我会很容易地承认我以前没有使用过 SOLID 原则,但从我对它们的了解来看,它们听起来不错)连接的角度。 本质上,通过查看系统,您应该能够找到内部高度耦合(许多频繁交互)但外部松散耦合(很少不频繁交互)的功能子集。 通常,任何大型代码库中都会包含一些这样的部分; 它们是切除的候选者。 本质上,一旦确定了候选人,您就必须枚举他们与整个系统的外部耦合点。 这应该可以让您很好地了解所涉及的相互依赖程度。 通常涉及相当多的相互依赖性。 评估子集及其连接点以进行重构; 经常(但并非总是)最终会出现一些清晰的结构重构,可以增加解耦。 着眼于这些重构,使用现有的耦合来定义允许子系统与系统的其余部分一起工作所需的最小接口。 寻找这些界面中的共性(通常,您会发现比您预期的更多的共性!)。 最后,实施您已确定的这些更改。
这个过程听起来很糟糕,但实际上,它实际上非常简单。 请注意,这并不是实现完全完美设计的系统的路线图(为此,您需要从头开始),但它肯定会降低整个系统的复杂性并提高代码的可理解性。
What I've done when presented with this type of thing (and I'll readily admit that I haven't used SOLID principles before, but from what little I know of them, they sound good) is to look at the existing codebase from a connectivity point of view. Essentially, by looking at the system, you should be able to find some subset of functionality that is internally highly coupled (many frequent interactions) but externally loosely coupled (few infrequent interactions). Usually, there are a few of these pieces in any large codebase; they are candidates for excision. Essentially, once you've identified your candidates, you have to enumerate the points at which they are externally coupled to the system as a whole. This should give you a good idea of the level of interdependency involved. There usually is a fair bit of interdependency involved. Evaluate the subsets and their connection points for refactoring; frequently (but not always) there ends up being a couple of clear structural refactorings that can increase the decoupling. With an eye on those refactorings, use the existing couplings to define the minimal interface required to allow the subsystem to work with the rest of the system. Look for commonalities in those interfaces (frequently, you find more than you'd expect!). And finally, implement these changes that you've identified.
The process sounds terrible, but in practice, it's actually pretty straightforward. Mind you, this is not a roadmap towards getting to a completely perfectly designed system (for that, you'd need to start from scratch), but it very certainly will decrease the complexity of the system as a whole and increase the code comprehensibility.
OOD - 面向对象设计
SOLID - 类设计
单一职责原则 - SRP - 由Uncle Bob 引入。 方法、类、模块只负责做单一事情(一项单一任务)
开放/封闭原则 - OCP - 由 Bertrand Meyer 提出。 方法、类、模块对扩展开放,对修改关闭。 使用继承、抽象、多态性、扩展、包装的力量。 [Java 示例],[ Swift 示例]
[Liskov 替换原理] - LSP - 由 Barbara Liskov 和 Jeannette Wing 提出。 子类型可以替代父类型而不会产生副作用
接口隔离原则 - ISP - 由Uncle Bob 提出。 你的界面应该尽可能小
[依赖倒置原则(DIP)] - DIP - 由Uncle Bob引入。 内部类、层不应该依赖于外部类、层。 例如,当您有
聚合
[关于]依赖项时,您应该而是使用一些抽象/接口。 [DIP vs DI vs IoC]关于包/模块(.jar、.aar、.framework)的 6 个原则:
做什么放在包内
包之间的耦合
[面向协议的编程(POP)]
OOD - Object Oriented Design
SOLID - class design
Single Responsibility Principle - SRP - introduced by Uncle Bob. Method, class, module are responsible only for doing single thing(one single task)
Open/Closed Principle - OCP - introduced by Bertrand Meyer. Method, class, module are open for extension and closed for modification. Use a power of inheritance, abstraction, polymorphism, extension, wrapper. [Java example], [Swift example]
[Liskov Substitution Principle] - LSP - introduced by Barbara Liskov and Jeannette Wing. A subtype can replace supertype without side effects
Interface Segregation Principle - ISP - introduced by Uncle Bob. Your interface should be as small as possible
[Dependency Inversion Principle(DIP)] - DIP - introduced by Uncle Bob. Internal class, layer should not be depended on external class, layer. For example when you have
aggregation
[About] dependency you should rather use some abstraction/interfaces. [DIP vs DI vs IoC]6 principles about packages/modules(.jar, .aar, .framework):
what to put inside a package
couplings between packages
[Protocol Oriented Programming(POP)]