Wednesday, May 30, 2007

Imposing Design (was Re: [TDD] Re: Wandering through layers of software)

[snipstart] --- In testdrivendevelopment@yahoogroups.com, "Kelly Anderson" <kellycoinguy@...> wrote: > > My TDD code throws more exceptions generally... so I'd really like to > hear more about your thoughts on this subject. > [snipend] Using the example as shown in: What is Exceptional? [126], The Pragmatic Programmer (converted from java to C#).

private TextReader ipstream;

public void open_passwd() { try { ipstream = new StreamReader(@"..\etc\passwd"); } catch(Exception) { throw new FileNotFoundException("No Password File Found"); } }

public bool open_user_file(string fname) { if (!File.Exists(fname)) return false;

ipstream = new StreamReader(fname);

return true; }

public void OpenUserFile(string fname) { if (!File.Exists(fname)) { Fire_FileDoesNotExistEvent(); return; }

ipstream = new StreamReader(fname); }

The method open_passwd has a dependency on passwd file. The second method is not dependent on a specific file. (In my opinion both methods are ripe to be refactored). Would you have written either of these methods with TDD/Refactoring. The point is that because of that dependency on the first method it has to have some way to communicate to the client that the part of the contract it is responsible for cannot be fulfilled. Also I am not saying that when you write code you shouldn't ever throw an exception.

As far as I have been able to determine there are only three standard ways objects can communicate in an OOP methodology.

  1. Exception Message (first method)
  2. Command Message (second method).
  3. Event Message (third method).
It is the third communication channel that for me is showing a lot of promise. It is the publish/subscribe pattern. The first two message type are hierarchical in nature, in other words you need the client to control those methods. While the third is collaborative. The method fires the event and forgets. The method does not care that a file does not exist. AND the client that called the third method does not have to be the one to handle the event message. When I first started on these groups I asked a question concerning an example Michael Feather's had concerning writing unit tests (A stack object). Ron Jeffries and others were kind enough to help me through the example, but one of the things that didn't sit well with me was that in explaining it to me Ron didn't bother with using the exception handling (Exception Message) he instead used the 'Command Message' (Query on state {IsEmpty})and then if IsEmpty == true then don't pop from stack. Now I want to create a contrived example. let us say we have a form with a button that when we click it it will use one of our (3) methods. Also if no file is found it will need to shutdown the system (Crash, Don't Trash [120], The Pragmatic Programmer) Now if you look in LoadFileForm class. Which of the click methods would you like to use? There is a lot of associated machinery that you have to add, every time you use the first two messages types. The first two message types create these dependencies for handling exceptions. The client really only is responsible for opening text files. Not handling errors. If you look at the unit tests you can see I was able to remove the SystemShutDown object from the constructor when using event messages. Also I was able to Subscribe a private method that also verified that the event was actually fired. In a normal application I really would have followed the Presenter First pattern. The final component then would have two presenters. These would wire up the form to the TextFileOpener and the TextFileOpener to SystemShutDown object. Because the Presenters are mappers. these three objects can change without affecting each other, because they initiate communication with each other via event messages. And anybody can subscribe to these events. I am not saying this type of programming Event Collaboration is a 'Silver Bullet'. But it is more along the lines of BDD. Other objects processing because of some other objects behavior.

using System; using System.IO; using NUnit.Framework;

namespace UnitTestAlarmSystem { [TestFixture] public class Test_FileOpener_NoExceptions { private const string FILE_NAME = "Tester"; private TextFileOpener textFileOpener; private ShutDownSystem shutDownSystem; private bool isNoFile;

[TestFixtureSetUp] public void TestFixtureSetUp() { textFileOpener = new TextFileOpener(); shutDownSystem = new ShutDownSystem(); }

[TestFixtureTearDown] public void TestFixtureTearDown() { shutDownSystem = null; textFileOpener = null; }

[Test] public void TestExceptionMessagesNoFile() { LoadFileForm form = new LoadFileForm(textFileOpener, shutDownSystem); form.ButtonClickException(); Assert.IsTrue(form.IsException); }

[Test] public void TestCommandMessageNoFile() { LoadFileForm form = new LoadFileForm(textFileOpener, shutDownSystem); form.SetFileName(FILE_NAME); form.ButtonClickCommand(); Assert.IsTrue(form.IsCommand); }

[Test] public void TestEventMessageNoFile() { isNoFile = false; textFileOpener.WhenLoadFile += new EventHandler(shutDownSystem.WhenNoFileHandler); textFileOpener.WhenLoadFile += new EventHandler(WhenNoFileHandler);

LoadFileForm form = new LoadFileForm(textFileOpener); form.SetFileName(FILE_NAME); form.ButtonClickEvent(); Assert.IsTrue(isNoFile); }

private void WhenNoFileHandler(object sender, EventArgs e) { isNoFile = true; } }

public class LoadFileForm { private TextFileOpener textFileOpener; private ShutDownSystem shutDownSystem; private string mFileName; private bool isCommand = false; private bool isException = false;

public LoadFileForm(TextFileOpener opener, ShutDownSystem shutDown) { textFileOpener = opener; shutDownSystem = shutDown; }

public LoadFileForm(TextFileOpener opener) { textFileOpener = opener; }

public bool IsException { get { return isException; } }

public bool IsCommand { get { return isCommand; } }

public void ButtonClickException() { try { textFileOpener.open_passwd(); //Process file } catch (Exception ex) { isException = true; shutDownSystem.SetException(ex.Message);

shutDownSystem.Execute(); } }

public void ButtonClickCommand() { bool fileOpenResult = textFileOpener.open_user_file(mFileName);

if (fileOpenResult) { //Process file } else { isCommand = true; shutDownSystem.SetException("No File Found {Command}"); shutDownSystem.Execute(); } }

public void ButtonClickEvent() { textFileOpener.OpenUserFile(mFileName); //Process file }

public void SetFileName(string file_NAME) { mFileName = file_NAME; } }

public class ShutDownSystem { private string mException;

public void Execute() { Console.WriteLine(mException); //Shut Down Logic }

public void WhenNoFileHandler(object sender, EventArgs e) { Console.WriteLine("No File Found {Event}"); //Shut Down Logic }

public void SetException(string exceptionMessage) { mException = exceptionMessage; } }

public class TextFileOpener { public event EventHandler WhenLoadFile;

private TextReader ipstream;

public TextReader Ipstream { get { return ipstream; } }

public void open_passwd() { try { ipstream = new StreamReader(@"..\etc\passwd"); } catch (Exception) { throw new FileNotFoundException("No File Found {Exception}"); } }

public bool open_user_file(string fname) { if (!File.Exists(fname)) return false;

ipstream = new StreamReader(fname);

return true; }

public void OpenUserFile(string fname) { if (!File.Exists(fname)) { Fire_FileDoesNotExistEvent(); return; }

ipstream = new StreamReader(fname); }

private void Fire_FileDoesNotExistEvent() { if (WhenLoadFile != null) WhenLoadFile.Invoke(null, null); } } }

No comments: