Friday, July 17, 2009

Day 37

This week my Dad taught a four day class to a couple of students, including myself, which was focused on the Agile Principles, Patterns, and Practices. The first day was a very quick run through on TDD so that the students would have the context under which all agile practices are performed. The most important information from that first day was the three rules of TDD: You may not write any production code until you have a test for it. You can only right as much of a test as is needed to fail, including compiler errors. You can only right as much production code as it takes the make the test pass. If you are using these three rules you are practicing TDD, and will experience the wealth of benefits that come along with extensive testing. What is interesting is how just following those three rules may actually lead you to many of the other agile practices like the Open/Closed Principle. A plethora of tests will force you to keep your code very flexible, because the act of writing a test forces you to access you production code from a separate path, often decoupling tight and nasty bonds.

The three days following were all about the agile PPP. The five PPP are:
Single Responsibility Principle
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle

SOLID

The SRP says that a class or a method can only have one reason to change. Every class needs to be based around one idea, and one idea only. Every method of that class should be geared to accomplish, manage, or add behavior to that one idea. A class named House should not, for example, have a method that tells the user what other houses or condos the owner of the house has. A method named makeGoldenFish should not set the fishing boat's x and y location. By using this principle we can avoid unexpected changes or failures when we create a certain class or use a certain method. It also makes sure that if we just so happened wanted to change the type of fishing boat, we wouldn't have to go into the makeGoldenFish method and change the boat type in there. It guarentees a certain degree of flexibilty in your code, as well as helping another coder understand what your code's intentions are.

The OCP says that classes or components should be open for extension but closed for modification. It should be easy to add new features to an existing framework, and you shouldn't have to change any of the existing code to do it. Making changes should be a low cost and low risk opperation. Adding new code is cheaper than changing old code, and by following the OCP you guarentee that adding new features is cheap, or at least cheaper than it would have been otherwise. Part of the process of writing good code is forgeting that details exist and writing very general solutions to problems, and then adding the details later. The OCP encourages you to write very abstract classes, methods, and solutions so that details can be added, interchanged, or removed without having to change much code at all. An example would be if I had some pets that I wanted to be able to command to move. I have a dog that runs, and fish that swims, and a bird that flies. A poor way to write the code to do this would be to have one Pet class, with some switch statement or if -else chain that checked what type of pet it was then said dog.run or bird.fly. This would violate the OCP because to add a new type of pet, like a sloth that can only walk, I would have to add another ifelse or case statement in every spot I wished to tell this pet to move. Thats a lot of changes, and very expensive, just to add a new pet. A better way to do this would be to use the Template Pattern. I would create an interface named Pet, and have a Dog, a Bird, and a Fish class which were derivatives of the Pet interface. I would then have a method in pet, which each derivative would implement, called move(). This way I could avoid all if and switch statements, and replace them all with pet.move(), and at runtime the appropriate pet would get the correct move call. This makes my code far more dynamic and flexible. To add a new pet, I would merely create a new derivative to Pet. Thats all. If the pet happened to be a sloth, then pet.move() would call the sloth's move function. This is cheap to add features too.

The LSP says that a derivative must be a sutable substitution for the base class. If I have a base class Hero with a method saveLife(Person) then my derivative Batman must have a saveLife(Person) method that will sufice in a situation that a user might call Hero.saveLife(Person). One must be careful with this however, for the IS-A relationship may apply for the real situation, but not the coded representation. The coded representation might not always match the reality. Some symptoms of the LSP are when low level details are forced to become visible, when bases know of their derivatives, when not all abstract methods are implemented in derivatives. A violation of the LSP often is a, or leads to a, violation of the OCP. An example is lets say you have a Driver base class and a Vehicle base class. Their derivatives are CabDriver ,Pilot and Car, Jet respectively. When driver.operate(vehicle) is called, you may have a CabDriver trying to operate a Jet, which simply wont work, and thus you have violated the LSP. To solve this you might use the Intelligent Children Pattern, which is useful when you have a dual higherarchy (such as the current example), and you don't want to have to use ifs or downcast your types. You can have the children, or the derivatives, hold the types of their respective needs. So the CabDriver would know his vehicle is a Car. This adds a dependancy, but there is a trade off either way.

The ISP says that an interface must be cohesive to the classes that implement them. When an interface begins to acquire more and more methods, and the classes that implement the interface don't use all of the methods, than the interface has gotten too fat. An interface should be specific to a general idea, and the classes that implement that interface should all fit into that idea. The example in Agile Software Development is a Door class, which is abstract, and has Door derivatives like a TimedDoor (a door that can only be open for a certain period of time). This TimedDoor uses the methods of a Door, but also inherets from a Timer class, to get the functionality it needs to know when it has been open too long. The example than explains a solution to this problem that demonstrates how an interface could be made for a TimerClient, which Door and then Timed Door would then implement. But why should Door implement a TimerClient? It shouldn't. A general Door has nothing to do with the timer, but in that solution it would implement the TimerClient just to get the timer functionality. This demonstrates at least part of the ISP. The ISP also outlines a strategy to write maintainable code. If you have some base class with a TON of methods, that a whole bunch of other classes use by extending the base class, then everytime you make a change to that base class you will need to recompile all of those children classes as well (even if they only use one of the base class methods). To solve this, you can stick an interface in between the base class and some of the child classes, so that the child classes will call the methods on the interface, and the base class will inherit the methods from the interface. This way you can free up some dependencies and only have to recompile small chunks of code if you make a change to the super base class.

No comments:

Post a Comment