Home > XP > Keep your code ignorant and lazy!

Keep your code ignorant and lazy!

November 26th, 2008

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:

  1. The user repository returns a blank password when a user cannot be found
  2. 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

XP , , , ,

  1. Fred
    November 27th, 2008 at 01:19 | #1

    Thanks for this great article ! Keep up the good work !

  2. Eric
    November 27th, 2008 at 08:35 | #2

    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.

  3. December 26th, 2009 at 15:58 | #3

    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

  4. February 12th, 2010 at 21:58 | #4

    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.

  5. March 10th, 2010 at 01:53 | #5

    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.

  6. March 12th, 2010 at 07:31 | #6

    That superb chapter must be appeared in pre written essay, because that is easy to buy essays choosing the good writing services.

  7. March 26th, 2010 at 02:26 | #7

    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!

  8. April 1st, 2010 at 10:59 | #8

    such a nice test to deal with bugs i have in my blog

  9. April 4th, 2010 at 12:23 | #9

    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.

  10. April 4th, 2010 at 21:48 | #10

    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.

  11. April 9th, 2010 at 17:27 | #11

    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.

  12. April 12th, 2010 at 00:36 | #12

    Great .Net code, I have test this

    Essay Writing

  13. May 27th, 2010 at 03:37 | #13

    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

  14. June 1st, 2010 at 03:35 | #14

    I give advice you to find buy essay and to be spare from scruple that your work will be done by essay writers

  15. June 4th, 2010 at 10:22 | #15

    Thank you, it’s very inspiring description about this good topic it might be very helpfu

  16. June 9th, 2010 at 05:19 | #16

    scruple that your work will be done by essay writers fast payday loans

  17. June 18th, 2010 at 03:51 | #17

    good topic it might be very helpfu

  18. June 23rd, 2010 at 00:44 | #18

    to be spare from scruple that your work will be done by essay writers

  19. June 23rd, 2010 at 02:29 | #19

    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.

  20. June 24th, 2010 at 01:33 | #20

    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.

  21. June 25th, 2010 at 05:22 | #21

    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.

  22. July 15th, 2010 at 00:26 | #22

    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.

  23. July 27th, 2010 at 21:05 | #23

    Its help me alot. I will make soon one with the help of your Busting the great TDD Myth

  24. July 28th, 2010 at 01:20 | #24

    An amazing machine, you deserve to 1. These fantastic shoes can be a brilliant and attractive. Enjoy and of itself.

  25. July 29th, 2010 at 01:56 | #25

    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….

  26. August 3rd, 2010 at 01:49 | #26

    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…

  27. August 10th, 2010 at 23:31 | #27

    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!

  28. August 11th, 2010 at 13:02 | #28

    Very good article.

  29. August 11th, 2010 at 13:05 | #29

    good article.

  30. August 14th, 2010 at 17:14 | #30

    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.

  31. August 21st, 2010 at 03:24 | #31

    Very Informative and nice post about “Keep your code ignorant and lazy!”.

  32. August 21st, 2010 at 05:17 | #32

    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.

  33. August 21st, 2010 at 20:52 | #33

    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.

  34. August 23rd, 2010 at 20:24 | #34

    You will order course work if you are not going to spoil your grades.

  35. August 24th, 2010 at 06:41 | #35

    You will of course work order if you do not go your degree theft.

  36. August 25th, 2010 at 00:39 | #36

    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.

  37. August 26th, 2010 at 22:50 | #37

    This Blog is truly a gold mine. I will actually try these tips and let you know how they work out! Thanks again mate.

  1. December 2nd, 2008 at 08:04 | #1
  2. January 7th, 2009 at 10:55 | #2

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word