Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to split code into components... big classes? small classes?

Tags:

c#

.net

This is very basic stuff, but here goes. I find that I am never able to agree with myself whether the way I split large classes into smaller ones make things more maintainable or less maintainable. I am familiar with design patterns, though not in detail, and also with the concepts of object oriented design. All the fancy rules and guidelines aside, I am looking to pick your brains with regards to what I am missing with a very simple, sample scenario. Essentially along the lines of: "...this design will make it more difficult to", etc... All the things I don't anticipate because of lack of experience essentially.

Say you need to write a basic "file reader / file writer" style class to process a certain type of file. Let's call the file a YadaKungFoo file. The contents of YadaKungFoo files is essentially similar to INI Files, but with subtle differences.

There are sections and values:

[Sections]
Kung,Foo
Panda, Kongo

[AnotherSection]
Yada,Yada,Yada
Subtle,Difference,Here,Yes

[Dependencies]
PreProcess,SomeStuffToPreDo
PreProcess,MoreStuff
PostProcess,AfterEight
PostProcess,TheEndIsNear
PostProcess,TheEnd

OK, so this can yield 3 basic classes:

public class YadaKungFooFile
public class YadaKungFooFileSection
public class YadaKungFooFileSectionValue

The two latter classes are essentially only data structures with a ToString() override to spit out a string list of values stored using a couple of Generic lists. This is sufficient to implement the YadaKungFooFile save functionality.

So over time the YadaYadaFile starts growing. Several overloads to save in different formats, including XML etc, and the file starts pushing towards 800 lines or so. Now the real question: We want to add a feature to validate the contents of a YadaKungFoo file. The first thing that comes to mind is obviously to add:

var yada = new YadaKungFooFile("C:\Path");
var res = yada .Validate()

And we're done (we can even call that method from the constructor). Trouble is the validation is quite complicated, and makes the class very large, so we decide to create a new class like this:

var yada = new YadaKungFooFile("C:\Path"); 
var validator = new YadaKungFooFileValidator(yada); 
var result = validator.Validate();

Now this sample is obviously terribly simple, trivial and insignificant. Either of the two ways above probably won't make too much difference, but what I don't like is:

  1. The YadaKungFooFileValidator class and the YadaKungFooFile class seem to be very strongly coupled by this design. It seems a change in one class, would likely trigger changes in the other.
  2. I am aware that phrases such as "Validator", "Controller", "Manager" etc... indicates a class that is concerned with the state of other objects as opposed to its "own business", and hence violates separation of concern principles and message sending.

All in all I guess I feel that I don't have the experience to understand all the aspects of why a design is bad, in what situations it doesn't really matter and what concern carries more weight: smaller classes or more cohesive classes. They seem to be contradictive requirements, but perhaps I am wrong. Maybe the validator class should be a composite object?

Essentially I am asking for comments on likely benefits / problems that could result from the above design. What could be done differently? (base FileValidator class, FileValidator Interface, etc.. u name it). Think of the YadaKungFooFile functionality as ever growing over time.

like image 484
Stein Åsmul Avatar asked Jun 25 '09 21:06

Stein Åsmul


2 Answers

Bob Martin wrote a series of articles on class design, which he refers to as the SOLID principles:

http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

The principles are:

  1. The Single Responsibility Principle: A class should have one, and only one, reason to change.
  2. The Open-Closed Principle: You should be able to extend a classes behavior, without modifying it.
  3. The Liskov Substitution Principle: Derived classes must be substitutable for their base classes.
  4. The Interface Segregation Principle: Make fine grained interfaces that are client specific.
  5. The Dependency Inversion Principle: Depend on abstractions, not on concretions.

So in light of those, let's look at some of the statements:

So over time the YadaYadaFile starts growing. Several overloads to save in different formats, including XML etc, and the file starts pushing towards 800 lines or so

This is a first Big Red Flag: YadaYadaFile starts out with two responsibilities: 1) maintaining a section/key/value data structure, and 2) knowing how to read and write an INI-like file. So there's the first problem. Adding more file formats compounds the issue: YadaYadaFile changes when 1) the data structure changes, or 2) a file format changes, or 3) a new file format is added.

Similarly, having a single validator for all three of those responsibilities puts too much responsibility on that single class.

A large class is a "code smell": it's not bad in and of itself, but it very often results from something that really is bad--in this case, a class that tries to be too many things.

like image 139
Tim Lesher Avatar answered Oct 07 '22 05:10

Tim Lesher


I don't think the size of a class is a concern. The concern is more cohesion and coupling. You want to design objects that are loosely coupled and cohesive. That is, they should be concerned with one well defined thing. So if the thing happens to be very complicated then the class will grow.

You can use various design patterns to help manage the complexity. One thing you can do for instance is to create a Validator interface and have the YadaKunfuFile class depend on the interface rather than the Validator. This way you can change the Validator class without changes to the YadaKungfuFile class as long as the interface does not change.

like image 35
Vincent Ramdhanie Avatar answered Oct 07 '22 04:10

Vincent Ramdhanie