Keep your code ignorant and lazy!
This post is a follow up to Busting the great TDD Myth. If you haven’t already read it, please check it out.
Lets review the code from the last post:
function TLogin.GetConnectionString : string; var reg : TRegIniFile; begin reg := TRegIniFile.Create('Software\MyApp'); Result := reg.ReadString('Database', 'ConnectionString', ''); FreeAndNil(reg); end; function TLogin.Execute(const UserName, Password : string) : Boolean; var UserQuery : TADOQuery; DBUserPassword : string; begin UserQuery := TADOQuery.Create(nil); try UserQuery.ConnectionString := GetConnectionString; UserQuery.SQL.Text := 'SELECT Password FROM Users WHERE UserName = ' + QuotedStr(UserName); UserQuery.Open; DBUserPassword := UserQuery.FieldByName('Password').AsString; UserQuery.Free; Result := DBUserPassword = Password; except Result := False; end; end;
Now lets review the problems:
- Hidden dependencies - TLogin does not publish the fact that it relies on a connection to the database and needs a connection string from the registry.
- Mixing concerns - Business logic and object creation occur in the same block of code. This means you can’t test business logic without creating the dependencies.
All of these problems can be summed up as: Too much ceremony and too little essence. What is this class trying to do? Does creating a connection to the database, querying a table and handling exceptions have much to do with the purpose of logging in to the system?
Before we start solving problems lets think about the elements involved with our task of logging in to the system. We have someone who wants to log in, we have a system for authenticating this person and we have system for providing the data against which this person will be authenticated. These are our domain objects: User, Login and User Repository.
NOTE: I don’t say Database because I don’t want my vocabulary to influence the implementation. I might use a database and I might not. For now I want to keep my options open.
We will begin by stripping away all the stuff in the login code that does not directly relate to the login business rules. That leaves us with:
function TLogin.Execute(const UserName, Password : string) : Boolean; begin Result := Password = DBPassword; end;
That’s it, really. All the other stuff has nothing to do with the business of logging in to the system. The old code looked for everything. So lets do the opposite. Lets demand everything we need:
function TLogin.Execute(User : TUser; const Password : string) : boolean; begin Result := Password = User.Password; end;
That’s considerably simpler. We even eliminated a test. We no longer have to test the TLogin object for a failure to connect to the database. We also eliminated two dependencies: the database and the registry.
What did we demand? We asked for something called TUser that includes a field called Password. The expectation is that this TUser construct should be the user in question and should include their saved password from the database.
This makes the tests very simple as well. All I need to do is supply a TUser (which I could easily create by hand) and a password against which to compare. So the question is: when this code is in production from where will the TUser come? Lets start with what TUser looks like right now:
type TUser = record Password : string; end;
All we need right now is the password field. Now lets look at the tests:
procedure TLoginTests.SetUp; begin FUser.Password := 'flinstone'; FLogin := TLogin.Create; end; procedure TLoginTests.TearDown; begin FreeAndNil(FLogin); end; procedure TLoginTests.TestGoodLogin; begin CheckEquals(True, FLogin.Execute(FUser, 'flinstone')); end; procedure TLoginTests.TestBadLogin; begin CheckEquals(False, FLogin.Execute(FUser, 'rubble')); end;
That’s considerably less code. It’s even pretty easy to read. So why is this better than the code from the previous post?
- There’s less of it
- It only performs the task at hand: logging in to the system
- It’s honest about what it does - there are no hidden dependencies
- You can now test the business logic in isolation
Now lets anwser the other burning question: In production, from where will we get the TUser that TLogin demands. Good question. Lets manifest some code:
procedure TUserRepositoryTests.SetUp; begin FRepo := TUserRepository.Create; end; procedure TUserRepositoryTests.TearDown; begin FreeAndNil(FRepo); end; procedure TUserRepositoryTests.TestValidUserLookup; begin user := FRepo.Lookup('fred'); CheckEquals('flinstone', user.Password); end;
The fake-it-til-you-make-it principle tells us we should do the following:
function TUserRepository.Lookup(const UserName : string) : TUser; begin Result.Password := 'flinstone'; end;
That’s enough code to make the test pass. Let’s add another test:
procedure TUserRepositoryTests.TestInvalidUserLookup; begin user := FRepo.Lookup('barney'); CheckEquals('', user.Password); end;
Our test tells us two things about the user repository business logic:
- The user repository returns a blank password when a user cannot be found
- A blank password is never valid for a user
Running the test confirms that it fails because of our fake implementation. Now we get solve the real problem: how to make TUserRepository flexible so that we can test drive it. TUserRespository needs to get its data from somewhere both in the test suite and in production.
ILoadable = interface procedure AddRecord(const UserName, Password : string); end; ILoader = interface procedure Load(Target : ILoadable); end;
NOTE: This seems to be referred to as the Data Provider or just Provider pattern.
An object implementing ILoader will be responsible for providing data to an object implementing ILoadable. An object implementing ILoadable will be responsible for asking ILoader to load it with data and storing that data. We’ll have TUserRepository implement ILoadable to ensure that it is data source agnostic. This will meet our goal of keeping TUserRepository test-drivable and production worthy. Let’s look at the implementation code:
TUserRepository = class(TInterfacedObject, ILoadable) private FUsers : TStringList; protected procedure AddRecord(const UserName, Password : string); public constructor Create(Loader : ILoader); destructor Destroy; override; function Lookup(const UserName : string) : TUser; end; implementation constructor TUserRepository.Create(Loader : ILoader); begin FUsers := TStringList.Create; Loader.Load(Self); end; destructor TUserRepository.Destroy; begin FreeAndNil(FUsers); end; procedure TUserRepository.AddRecord(const UserName, Password : string); begin FUsers.Add(Format('%s=%s', [UserName, Password])); end; function TUserRepository.Lookup(const UserName : string) : TUser; begin Result.Password := FUsers.Values[UserName]; end;
I think you’ll agree that we’ve done the minimal amount of work here. Right now all we care about is looking up a user by their user name and providing the associated password. As such, a TStringList will do.
Now we’ve introduced a dependency. TUserRespository depends on an ILoader to provide its data. How is that any different than the code from the previous post? First, TUserRepository is honest about its dependency; you can’t create it without providing an ILoader. Second, rather than have TUserRepository demand a concrete object it asks for an interface. This is good because TUserRepository has to know nothing about the actual implementation of ILoader or even from where the data is being provided.
Since we’ve added this dependency we need to update our test:
implementation type TTestData = class(TInterfacedObject, ILoader) protected procedure Load(Target : ILoadable); end; procedure TTestData.Load(Target : ILoadable); begin Target.AddRecord('fred', 'flinstone'); end; procedure TUserRepositoryTests.SetUp; begin TestData := TTestData.Create; FRepo := TUserRepository.Create(TestData); end; procedure TUserRepositoryTests.TearDown; begin FRepo := nil; end;
We needed to provide TUserRepository with an ILoader. Because we want to control the data we create a stub ILoader with TTestData that only loads the ILoadable with one record. In production we would need to create TUserRepository with an ILoader that provides data from the production data source. This is where we might connect to a database and query a table. I’ll leave how to create and test this portion of the code for another post.
Take aways
How did we solve our problems?
- Dependency injection. Rather than have our code look for or create what it needed we demanded everything upfront. We kept our code as ignorant as possible. This helped us separate the business logic from object creation and allowed us to test the business logic in isolation.
- Inversion of control. Each piece of code asked its dependencies to do all the work. Our code was not only ignorant, it was also as lazy as possible.
- Separation of concerns. Each piece of code only implemented the business logic relevant to its task. Each of the three domain objects did only what was needed and left the details to its dependencies. None of them meddled in the affairs of the others.
The wrap-up
In the next post I’ll cover how to use FitNesse to test the code that will provide live data to the TUserRepository.
I hope you enjoyed the article.
Additional information
Check out this Google talk: Clean Code Talks - Dependency Injection
Get the slides here: Link
Check this article: Ending Legacy Code in our lifetime
Thanks for this great article ! Keep up the good work !
All this boils down to the #1 rule of programming:
The only code that is guaranteed to have no bugs is the code that doesn't exist.
and its interpretation for tests and debugging:
The most efficient way to get rid of bugs is to use the "Delete" key.
It’s not so simple to bring a professional custom essay, first of all if you are concerned. I consult you to set buy custom essay papers and to be spare from query that your work will be done by writing services
Hi,
Such interesting read and information, thanks for sharing this post, I’ve already bookmarked your blog. I can see that you are putting a lot of time and effort into your blog and detailed articles! I am deeply in love with every single piece of information you post here. Will be back often to read more updates.
Here is plenty fire impulse in the world to get anybody at all through a buy thesis. You have to count on flawless strain of habit.
That superb chapter must be appeared in pre written essay, because that is easy to buy essays choosing the good writing services.
Thanks for post. It’s really informative stuff.
I really like to read.Hope to learn a lot and have a nice experience here! my best regards guys!
such a nice test to deal with bugs i have in my blog
Thank you, it’s very inspiring description about this good topic it might be very helpful for students.
Recently i needed resume. To my admirable surprise, resume was honest the price I paid for it.
You have to be definitely a good professional as the masters at the ringtones download or just voice ringtones sites, to create your very good outcome. Not every man will.
Here’re extraordinary suggestions about the way to have the great degree. Thence, people have to read the idea just about this topic and just accomplish the good enough humanities essay. The more easy way is to choose the experienced an essay writing service and just purchase free essays there. Hope this would help people.
Great .Net code, I have test this
Essay Writing
It’s not so simple to bring a good enough custom written essay, especially if you are busy. I give advice you to find buy essay and to be spare from scruple that your work will be done by essay writers
I give advice you to find buy essay and to be spare from scruple that your work will be done by essay writers
Thank you, it’s very inspiring description about this good topic it might be very helpfu
scruple that your work will be done by essay writers fast payday loans
good topic it might be very helpfu
to be spare from scruple that your work will be done by essay writers
good enough humanities essay. The more easy way is to choose the experienced an essay writing service and just purchase free essays there. Hope this would help people.
Thank you very much for your topic about this good topic! I could order custom written essay or buy custom essay papers at the term paper writing services.
I was doing several of research looking at some other term papers service. and this is the fifth link to the search essay about this good topic as relayed by Google… so you are acceptable that you are care a free service for them and improve their traffic. So if you chastely support this then you should take the money but if your conclusion has crack at all and you’re against it then you should assumably eliminate them. But it’s still yours to commit.
Classic exposition, I have also mentioned it in my blog article. But it is a pity that almost no friend discussed it with me. I am very happy to see your article.
Its help me alot. I will make soon one with the help of your Busting the great TDD Myth
An amazing machine, you deserve to 1. These fantastic shoes can be a brilliant and attractive. Enjoy and of itself.
I suggest this site to my friends so it could be useful & informative for them also.I love flowers…I am also interested to send flowers all over the world….
I suggest this site to my friends so it could be useful & informative for them also.I love flowers…I am also interested to send flowers all over the world…
This is very informative and knowledge able.I am hoping the same best work from you in the future as well. Thank you very much for about this good topic!
Very good article.
good article.
If you’re in not good state and have got no cash to get out from that point, you will have to take the credit loans. Just because that should help you unquestionably. I get credit loan every time I need and feel good just because of that.
Very Informative and nice post about “Keep your code ignorant and lazy!”.
I found your website perfect for my needs. It contains wonderful and helpful posts. I have read most of them and got a lot from them.
To reach success, some students have to state if they want to perform the papers for money or buy custom writing services of really good quality.
You will order course work if you are not going to spoil your grades.
You will of course work order if you do not go your degree theft.
This is what I was seeking for a while! Appreciate for this article around study! One day someone said that In union there is might. Our high trained team can support you in writing buy term paper.
This Blog is truly a gold mine. I will actually try these tips and let you know how they work out! Thanks again mate.