OK, that's a got scope for a book. A long book.
But I remember a long time ago I did a small fixed price contract for a company. They wanted me to add some "simple" functionality to an existing app. They had the source, but no-one to do the work.
I'd never done a fixed price contract before, but I knew I'd never got any estimates right. I didn't like the idea of a fixed price contract much.
I started with - If you want a fixed price, pay me for one day to do the spec & estimates. This was (very) grudgingly accepted.
I sat down and broke down what needed to be done. With each part, I continued to break it down to the level where I knew exactly what was needed, and I knew that part would take less than 1/2 a day.
Anything longer than a Day was broken down more.
I went back with the estimate. I think it was about 3 weeks work. It's a long time ago. The manager thought I was winding him up, he'd expected about 3 days, not 3 weeks.
I had brought my list.
We went though the list piece by piece, and about 1/3 of the way through he said "Fine".
It took 12 days, not 15.
This was a task which had no unknowns for me. Each individual part was something I'd done already somewhere else, or at some other time.
Consider the amount of work that would be required to estimate a 3 month project for 6 people, working with 3 new pieces of technology that no-one on the team has used before. If you have not done something before, with a particular tool, it is possible that there's a snag. Do a simple test. Can I serialise the data as XML? Opps, the files are HOOOGE. We have to use something else.
Everyone wants accurate estimates, but no-one wants to put in the leg work to come up with the estimates.
You can of course still be wrong, but hopefully, not off by a factor of 5.
There are lots of books and websites about software design. But often, the devil is in the details. Look after the pennies and the pounds look after themselves. There are a whole lot of little thing that if you pay attention to them, it just makes life a little easier. This is about the little things.
Monday, May 6, 2013
Friday, March 15, 2013
Church and State
It's been a while. Which means I've probably been having too much fun doing other stuff.
We recently saw the election of a new Pope, and I was thinking about some countries which embed their religion into the law of the land. The USA makes a big deal about separation of church and state, and I think that's a good thing.
In software we often make a token effort to separate things. How many people remember their first program in college looking something like
StreamReader streamReader = new StreamReader(_fileName);
while (streamReader.Peek() >= 0) {
string line = streamReader.ReadLine();
if (line != null){
IEnumerable data = parseLine(line);
double sum = 0;
foreach (double d in data){
sum += d;
}
Console.WriteLine(sum);
}
}
So right from the start, we teach kids to bind Where we get the data, What we do with it, and Where we send the results.
Separation of functionality?
var results = enumerableFile.Select(
We recently saw the election of a new Pope, and I was thinking about some countries which embed their religion into the law of the land. The USA makes a big deal about separation of church and state, and I think that's a good thing.
In software we often make a token effort to separate things. How many people remember their first program in college looking something like
StreamReader streamReader = new StreamReader(_fileName);
while (streamReader.Peek() >= 0) {
string line = streamReader.ReadLine();
if (line != null){
IEnumerable
double sum = 0;
foreach (double d in data){
sum += d;
}
Console.WriteLine(sum);
}
}
So right from the start, we teach kids to bind Where we get the data, What we do with it, and Where we send the results.
Separation of functionality?
- We could get the data and pass in an iterator
- We could wrap the stream in a class which implements an IEnumerable
- We could write a separate class parse Class, and pass it in.
- We could write a separate sum() action and pass that in.
- We could return the result as an iterator, which would execute as we step through.
We could end up with 4 separate, useful small classes which could be reused and mixed to suit our needs.
We could even hand off the Loop to Linq.
Imagine the resulting code
enumerableFile = new EnumerableFile("sample.txt");
var results = enumerableFile.Select(
s => ParseStringToDoubleList.parseLine(s).Sum()
);
Now I can write unit tests for ParseStringToDoubleList() - Ok, an appalling name, but that's an others days work.
I can use Linq's Sum function.
If I want averages instead, no problem.
I now have a tool-kit to build functionality
First we learn to build programs, then we learn to build tools to make better programs.
Thursday, July 26, 2012
Ceci n'est pas une interface
Likewise, extracting all the public functions from a class and surrounding them with
public interface ISomeClassName {
}
Does not make an interface.
One of the terrible failings of Resharper is the Extract Interface dialog has a button for All Public functions.
This encourages this frankly misguided behaviour.
IEnumerable
If I take a Binary Tree, there may be many different functions, allowing me to add Ranges, delete Ranges, create Views, Find Items, Get Counts, etc etc. How do I benefit from extracting the entire public interface to create an IBinaryTree. It would likely turn out that the interface has so much in it that the only thing which can implement it is another Binary Tree.
Maybe I have a lump of code which wants a Binary Tree, but in fact it really only cares about a collection which has been sorted into a given order. SortedCollection (or if you must ISortedCollection) might be a perfectly reasonable interface.
When we look at the members that my code uses, it comes down to iterating through the list...
public interface ISortedCollection
IEnumerator
}
So in fact my code just needs an IEnumerable
If I pass in an IEumerable
Now we are down to what an interface is. An interface specifies some functionality that I want to use. Ideally the minimal functionality. It is so much less than a list of public functions in an existing class, and in being less, it is more.
Now my Unit Tests can be far simpler, I no longer need to have test which depend on the binary tree. I can use a simple Array which I define in sorted order.
Small interfaces reduce unnecessary binding.
* You could argue that a specific interface for Sorted data would allow me to catch, at compile time, the problem of passing unsorted data down here. This is true, but that's a whole other post.
Friday, July 6, 2012
Open/Closed Principle
With so many things interrelating, it's hard to explain the importance of one thing without relying on many other bits of knowledge.
Open/Closed is on of those things.
How can software be Open for Addition and Closed for Modification.
Surely I need to change code? And change it all the time.
Well, maybe.
If I write smaller functions, and smaller classes. If they do one thing, and do it correctly, then they probably don't need to change. Ever.
My larger blocks of functionality, which are made of the smaller blocks, will change more often, but if, for example I create a class or function to do a bilinear interpolation of a surface, then as long as it does a bilinear interpolation, I may need to optimize it, but it will only ever do a bilinear interpolation. If I want to do some other interpolation, I create a new class.
My higher level class may change to use the new interpolation, but I have not CHANGED the bilinear one.
I may wrap the General Bilinear interpolation in a class which is better named from the point of view of the problem domain, and takes domain named parameters. Rather than have an Bilinear.Interpolate(double x, double y)->double my wrapper may be myChemicalMix.ApproximateReactionRate(double tempInCelcius, Acidity ph) -> ReactionRate.
Now my domain class does nothing but map my domain problem onto an interpolation. I no longer have to remember is x the Acidity or the Temperature.
So now when I change from a Bilinear Interpolation to a Triangular Interpolation, my Bilinear Class remains untouched, I write my Triangular Interpolation and Unit Tests, and if it turns out that they can each implement a Surface Interpolation interface, happy days.
Nothing in designing software stands on it's own. That's why it's easy to write a program, but it's hard to engineer one.
Without Single Responsibility and Dependency Inversion, Unit testing is staggeringly difficult.
Without The Open/Closed Principle, you will spend all your time changing tests.
Without Interface segregation, you won't understand what you need to mock out for a given test, and the test will be overly complex.
Good design can only come from understanding why.
* yes, I did make those up from thin air
Open/Closed is on of those things.
How can software be Open for Addition and Closed for Modification.
Surely I need to change code? And change it all the time.
Well, maybe.
If I write smaller functions, and smaller classes. If they do one thing, and do it correctly, then they probably don't need to change. Ever.
My larger blocks of functionality, which are made of the smaller blocks, will change more often, but if, for example I create a class or function to do a bilinear interpolation of a surface, then as long as it does a bilinear interpolation, I may need to optimize it, but it will only ever do a bilinear interpolation. If I want to do some other interpolation, I create a new class.
My higher level class may change to use the new interpolation, but I have not CHANGED the bilinear one.
I may wrap the General Bilinear interpolation in a class which is better named from the point of view of the problem domain, and takes domain named parameters. Rather than have an Bilinear.Interpolate(double x, double y)->double my wrapper may be myChemicalMix.ApproximateReactionRate(double tempInCelcius, Acidity ph) -> ReactionRate.
Now my domain class does nothing but map my domain problem onto an interpolation. I no longer have to remember is x the Acidity or the Temperature.
So now when I change from a Bilinear Interpolation to a Triangular Interpolation, my Bilinear Class remains untouched, I write my Triangular Interpolation and Unit Tests, and if it turns out that they can each implement a Surface Interpolation interface, happy days.
Nothing in designing software stands on it's own. That's why it's easy to write a program, but it's hard to engineer one.
Without Single Responsibility and Dependency Inversion, Unit testing is staggeringly difficult.
Without The Open/Closed Principle, you will spend all your time changing tests.
Without Interface segregation, you won't understand what you need to mock out for a given test, and the test will be overly complex.
Good design can only come from understanding why.
* yes, I did make those up from thin air
Tuesday, June 5, 2012
What's a tractor for
Imagine that you teach someone how to drive a tractor, how to hook up a plough, and you send them out into the field.
At no point do you mention crops, seeds, harvesting, food, or seasons. You don't speak of fertiliser nor crop rotation.
When you come back the field is full of staggering, incredibly complex and mostly useless furrows.
In the same way we seem to teach people how to use polymorphism, lambda functions, inheritance, iterators, and other tools.
But somewhere along the way no one has pointed out the fact that what we are doing is trying to make it easier to write and maintain code. In addition, it would be nice if we could also make it easier to be sure the code is correct.
It seems that we have taught the how, not the why.
In the good old days of C++ the general comment was that in your first program you learn how to overload the operators, in your second program, you learn not to.
At no point do you mention crops, seeds, harvesting, food, or seasons. You don't speak of fertiliser nor crop rotation.
When you come back the field is full of staggering, incredibly complex and mostly useless furrows.
In the same way we seem to teach people how to use polymorphism, lambda functions, inheritance, iterators, and other tools.
But somewhere along the way no one has pointed out the fact that what we are doing is trying to make it easier to write and maintain code. In addition, it would be nice if we could also make it easier to be sure the code is correct.
It seems that we have taught the how, not the why.
In the good old days of C++ the general comment was that in your first program you learn how to overload the operators, in your second program, you learn not to.
Friday, April 13, 2012
Design V Hack
Sometimes, it makes sense to do it quick and dirty. If you need a demo by Tuesday to get a foot in the door, or if you're not even sure what you are doing. Bang something together figure it out, write it properly later.
The cost of banging it out any old way that works is simply deferred. Later you pay the cost, either in a re-write or in maintenance nightmares.
If your "feature" has infected a lot of the rest of the code base, then a rewrite is expensive. If it's in structural code, which everyone depends on, then that nasty interface is already in use, and now, not only do you pay back the work you saved, you also pay back the interest and penalties. (You may have thought that credit cards were harsh). There's even a term for this :- Technical Debt
As you add hack after hack to a project, or even just messy code that's difficult to follow, the savings start to disappear. Now to change something, you need to spend more time understanding difficult code. Now it takes more time to change anything. The process continues until the code becomes intractable. Then each bug fix introduces new problems. Each change takes longer and longer.
People say they have no time for refactoring, automated testing, and just plain writing better code. I'd argue, you don't have time not to.
Wednesday, March 7, 2012
The Architectural IF
"IF" is one of the fundamental constructs of programming languages. (Yes, I know someone will know at least 3 languages without "IF", but I know a lot more which have "IF").
"IF", like a Biro is so ubiquitous that it's pretty much impossible to imagine life without it.
But as the Biro has been superseded by the Computer for works of any length, "IF" has been superseded in many ways by better ways of doing things.
If makes a nice living dealing with details. But often we can avoid it by simple design
What if GetClientList() never returned a Null. If something goes wrong, it throws and exception, if there's no clients, it returns an empty list. Now my Code looks like
This is a flavour of the null object pattern. There is no decision to make. There is nospoon "IF".
But that's a detail, it's the The Architectural "IF" that 'causes me to occasionally consider inventive and infantile revenges on both other programmers, and in fairness, on my past self. If you find a comment like this..
The comment indicates there is so much code dependent on the "IF" that you cannot see the other brace on screen. It also darkly hints at a programmer cheerfully ignoring virtual functions.
So why is the programmer doing this?
Surely (s)he:
It may even be an "else if".
If your Architecture is creaking, "IF" can fix it for a while, but like using sticky-tape, the repair is at best temporary.
"IF", like a Biro is so ubiquitous that it's pretty much impossible to imagine life without it.
But as the Biro has been superseded by the Computer for works of any length, "IF" has been superseded in many ways by better ways of doing things.
If makes a nice living dealing with details. But often we can avoid it by simple design
var myClientList = GetClientList();
if (myClientList != null)
{
sendAnnoyingEmailsTo(myClientList);
}
What if GetClientList() never returned a Null. If something goes wrong, it throws and exception, if there's no clients, it returns an empty list. Now my Code looks like
var myClientList = GetClientList();
sendAnnoyingEmailsTo(myClientList);
This is a flavour of the null object pattern. There is no decision to make. There is no
But that's a detail, it's the The Architectural "IF" that 'causes me to occasionally consider inventive and infantile revenges on both other programmers, and in fairness, on my past self. If you find a comment like this..
} // closing if (clientObject is typeof(MajorClient))
The comment indicates there is so much code dependent on the "IF" that you cannot see the other brace on screen. It also darkly hints at a programmer cheerfully ignoring virtual functions.
So why is the programmer doing this?
Surely (s)he:
- cannot be ignorant of virtual functions?
- cannot fail to realise that a new dependency is being propagated throughout the code?
- must know that now every new Client Sub Class will need to be weaved in through every file that uses the "construct"?
It may even be an "else if".
If your Architecture is creaking, "IF" can fix it for a while, but like using sticky-tape, the repair is at best temporary.
Subscribe to:
Posts (Atom)