I’ve bought the book “Beautiful Code” published by O’Reilly and edited by Andy Oram and Greg Wilson and it has inspired my work in the last days. It doesn’t actually teach writing beautiful code or it would just be another GoF or Fowler book, it teaches you how the best think and it puts you thinking like the best. Hopefully thinking like the best will make you one of the best :).
Ten years ago when I left school if one would ask me where the best programmers spend their time I guess that I would answer writting very complex very fast. Yet today I spent hours thinking on an interface and how would people use it. I did that because I realized (through the book) that the best delete code instead of writing it. Code is bad! Code is ugly! Code is money spent! The less the better! What I really want is to reuse the same code over and over to solve equivalent problems by hiding the details.
What I was trying to do is to bridge the gap between the way you change collections of objects and the way you use RIA services to change tables. I looked at IEnumerable<T>, at ICollection<T>, at IList<T>, at EntitySet<T>, all the change tracking interfaces and the paged collection interfaces and at the end the only phrase on my brain was what a mess!
It is amazing how a mess can be created from a very simple mistake, the idea that operations that act on sets are synchronous. Have you ever asked yourself why would Add return void? Why would it return “immediately”? Imagine you are adding one element to a collection that as 10^9 elements and must always be sorted, it can take quite some time until add returns control to the caller. Until that happens the caller is blocked, wasting resources doing nothing. But if you say Add cannot be synchronous what would it look like? Well if I was on the Desktop market then I would say Task<T> but since I am on the Silverlight I must come up with a different name to the same idea because I don’t want to clash with that name in the future. So I came up with IResult:
/// <summary> /// Represents the result of a method that cannot be completed synchronously. /// </summary> /// <remarks> /// For void methods we consider that if no Exception was thrown the method executed successfully. /// </remarks> public interface IResult { /// <summary> /// Occurs when all the activities initiated by the method that return this instance are completed. /// </summary> event EventHandler<EventArgs> Completed; /// <summary> /// Gets the error that ocurred during the execution of the activities triggered by the method that /// returned this instance or null if no error occurred. /// </summary> Exception Error { get; } /// <summary> /// Gets a value indicating whether the method that returned this instance terminated in error. /// </summary> bool HasError { get; } /// <summary> /// Gets a value indicating whether the method that returned this instance finished its execution. /// </summary> bool IsComplete { get; } /// <summary> /// Gets a tag that identifies the method that returned this instance. /// </summary> string Action { get; } /// <summary> /// Gets a value representing the input of the method that returned this instance. /// </summary> IEnumerable Request { get; } /// <summary> /// Blocks the current thread until the underlaying activities complete. /// </summary> void Wait(); /// <summary> /// Blocks the current thread until the underlaying activities complete but setting a timeout. /// </summary> /// <param name="miliseconds">The amount of time in miliseconds that the current thread will wait for a result.</param> void Wait(int miliseconds); }
Ok, now that we have IResult how would a Repository contract look like?
/// <summary> /// A set of elements that cannot be changed synchronously and that interfaces with a persistent storage /// using a collection like interface. /// </summary> /// <typeparam name="T">The Type of elements in the repository.</typeparam> public interface IRepository<in T> { /// <summary> /// Gets a value indicating whether the repository can be modified. /// </summary> bool IsReadOnly { get; } /// <summary> /// Gets a value indicating whether the repository was modified. /// </summary> bool IsChanged { get; } /// <summary> /// Gets a value indicating whether the repository allows adding elements. /// </summary> bool CanAdd { get; } /// <summary> /// Gets a value indicating whether the repository allows removing elements. /// </summary> bool CanRemove { get; } /// <summary> /// Gets the number of elements in the set asynchronously. /// </summary> IResult<int> Count { get; } /// <summary> /// Adds the given element to the set asynchronously. /// </summary> /// <param name="item">The element to add to the set.</param> /// <returns></returns> IResult Add(T item); /// <summary> /// Determines asynchronously whether the given element is in the set or not. /// </summary> /// <param name="item">The element to check whether it is on the set or not.</param> /// <returns>A representation of the current state of the activities initiated by the method call.</returns> IResult<bool> Contains(T item); /// <summary> /// Removes asynchronously the element from the set. /// </summary> /// <param name="item">The element to add to the set.</param> /// <returns>A representation of the current state of the activities initiated by the method call.</returns> IResult Remove(T item); /// <summary> /// Accepts the changes made to the set making them permanent. /// </summary> /// <returns>A representation of the current state of the activities initiated by the method call.</returns> IResult AcceptChanges(); /// <summary> /// Discards the changes made to the set reverting to the values that where previously on the persistent media. /// </summary> /// <returns>A representation of the current state of the activities initiated by the method call.</returns> IResult RejectChanges(); }
I would recommend you to spend a couple of minutes thinking on Count. In a standard collection Count would return the number of elements that are in the data structure holding the data, but when we are counting records on a table through a service call that can take quite some time. It makes sense to return an IResult. When the Count operation is completed then the Completed event will be raised and the program can use that value. Until that happens it can do other stuff. In the next article we will see how we can tweak this interface to take advantage of pull base mechanisms and reactive extensions. I’m building a framework based on these ideas and I will release the source code if someone is interested in it.
No comments:
Post a Comment