Udostępnij za pośrednictwem


CCR tips and tricks - part 5

There is one thing in CCR I think should not be there in its current form and that is the SuccessFailurePort. The reason I don't like its current implementation is that it can easily become a generic container of results where really other types should be used. Before I give an example you need to know what setup I used for all tests in today's post.

  1:     [TestClass]
 2:     public class SuccessFailurePortUsage
 3:     {
 4:         private DispatcherQueue dispatcherQueue = new DispatcherQueue();
 5:         private PortUtility portUtility;
 6:  
 7:         [TestInitialize]
 8:         public void Setup()
 9:         {
 10:             portUtility = new PortUtility(dispatcherQueue);
 11:         }

Now an example on what it looks like when the SuccessFailurePort is misused.

  1:         private SuccessFailurePort WhenItShouldNotBeUsed(string numberOfThingsOk)
 2:         {
 3:             var resultPort = new SuccessFailurePort();
 4:             int thingsOk = 0;
 5:             if (int.TryParse(numberOfThingsOk, out thingsOk))
 6:             {
 7:                 resultPort.Post(new SuccessResult(thingsOk));
 8:             }
 9:             else
 10:             {
 11:                 resultPort.Post(
 12:                     new Exception("Somehting is not OK: " + numberOfThingsOk));
 13:             }
 14:             return resultPort;
 15:         }
 16:  
 17:         [TestMethod]
 18:         public void When_it_should_not_be_used()
 19:         {
 20:             using (var testCausality = new CausalityForTests(dispatcherQueue))
 21:             {
 22:                 portUtility.Choice(
 23:                     WhenItShouldNotBeUsed("42"), 
 24:                     s => Assert.AreEqual(42, s.Status), 
 25:                     e => Assert.Fail("Unexpected failure: {0}", e));
 26:                 portUtility.Choice(
 27:                     WhenItShouldNotBeUsed("Not a number"), 
 28:                     s => Assert.Fail("Unexpected success"), 
 29:                     CcrServiceBase.EmptyHandler);
 30:             }
 31:         }

As you can see the status field of the SuccessResult is used as a transport for a result. The correct PortSet to used in this case is really PortSet<int, Exception>. There is also the opposite situation where a SuccessFailurePort is not used but it really should be used. Typically it is a PortSet<bool, Exception> port where false (or true) is never actually posted like in this example.

  1:         private PortSet<bool, Exception> WhenItShouldBeUsed(bool allOk)
 2:         {
 3:             var resultPort = new PortSet<bool, Exception>();
 4:             if (allOk)
 5:             {
 6:                 resultPort.Post(true);
 7:             }
 8:             else
 9:             {
 10:                 resultPort.Post(new Exception("Somehting is not OK"));
 11:             }
 12:             return resultPort;
 13:         }
 14:  
 15:         [TestMethod]
 16:         public void When_it_should_be_used()
 17:         {
 18:             using (var testCausality = new CausalityForTests(dispatcherQueue))
 19:             {
 20:                 portUtility.Choice(
 21:                     WhenItShouldBeUsed(true), 
 22:                     CcrServiceBase.EmptyHandler, 
 23:                     e => Assert.Fail("Unexpected failure: {0}", e));
 24:                 portUtility.Choice(
 25:                     WhenItShouldBeUsed(false), 
 26:                     s => Assert.Fail("Unexpected success"), 
 27:                     CcrServiceBase.EmptyHandler);
 28:             }
 29:         }

In my opinion the only time you should use a SuccessFailurePort is when you use the shared instance like this.

  1:         private SuccessFailurePort TheRightWay(bool allOk)
 2:         {
 3:             var resultPort = new SuccessFailurePort();
 4:             if (allOk)
 5:             {
 6:                 resultPort.Post(SuccessResult.Instance);
 7:             }
 8:             else
 9:             {
 10:                 resultPort.Post(new Exception("Somehting is not OK"));
 11:             }
 12:             return resultPort;
 13:         }
 14:  
 15:         [TestMethod]
 16:         public void The_right_way()
 17:         {
 18:             using (var testCausality = new CausalityForTests(dispatcherQueue))
 19:             {
 20:                 portUtility.Choice(
 21:                     TheRightWay(true), 
 22:                     CcrServiceBase.EmptyHandler, 
 23:                     e => Assert.Fail("Unexpected failure: {0}", e));
 24:                 portUtility.Choice(
 25:                     TheRightWay(false), 
 26:                     s => Assert.Fail("Unexpected success"), 
 27:                     CcrServiceBase.EmptyHandler);
 28:             }
 29:         }

This is why I think the SuccessFailurePort is implemented in the wrong way. Since only the shared instance is used the SuccessFailurePort could have been implemented using the EmptyValue type like this.

  1:         private PortSet<EmptyValue, Exception> TheRightWayAlternative(bool allOk)
 2:         {
 3:             var resultPort = new PortSet<EmptyValue, Exception>();
 4:             if (allOk)
 5:             {
 6:                 resultPort.Post(EmptyValue.SharedInstance);
 7:             }
 8:             else
 9:             {
 10:                 resultPort.Post(new Exception("Somehting is not OK"));
 11:             }
 12:             return resultPort;
 13:         }
 14:  
 15:         [TestMethod]
 16:         public void The_right_way_alternative()
 17:         {
 18:             using (var testCausality = new CausalityForTests(dispatcherQueue))
 19:             {
 20:                 portUtility.Choice(
 21:                     TheRightWayAlternative(true), 
 22:                     CcrServiceBase.EmptyHandler, 
 23:                     e => Assert.Fail("Unexpected failure: {0}", e));
 24:                 portUtility.Choice(
 25:                     TheRightWayAlternative(false), 
 26:                     s => Assert.Fail("Unexpected success"), 
 27:                     CcrServiceBase.EmptyHandler);
 28:             }
 29:         }

Notice the code on line 27 in the last example? That's another good practice in CCR code. Whenever you have a handler that is empty you should use CcrServiceBase.EmptyHandler.