“We have to do {Insert Questionable Practice Here} in order to be consistent with the rest of the application.”
I’m so tired of hearing this statement. When a statement such as this is uttered, it is intended to do three things: 1) brush aside the necessity for a design change, 2) give the illusion of admitting that something is wrong with the current design (or lack thereof) while at the same time 3) justifying the current design.
“Consistency” is not a design principle. “Consistency” is a virtue if and only if you are following design principles, or style guidelines. “Consistency” describes your attitude toward good design principles and coding standards. It presumes the existence of good design principles and coding standards, and can never be used as a replacement for them. Who can count the coding crimes that have been committed in the name of “consistency?” The intent of a statement like this is to obliterate the distinction between good and bad design, and make bad design seem practical.
It isn’t.
Don’t fall for it. Repeating the same awful mess over and over again can never be good.
As the requirements put pressure on your design to change, you should change it. You should change it immediately. If the change is large, you may have to plan the change and execute it in stages, keeping the code functional and migrating it over time—but you should start immediately. In addition, you should generally complete one design change before embarking on another—at least if it’s in the same area of your application.
If you’re working in a team environment, you will need to communicate the necessity of medium or large-scale design shifts to your team. You should listen to possible objections (after all you may be missing some important information). If the change is small (meaning its scope is localized to the particular task you are working on), just do it. If the team is not generally receptive to improving the design of the code, either change the team or change teams.
You should consistently improve the design of code in the face of new requirements. You should consistently apply SOLID, DRY, YAGNI, TDD and other design and process principles to all new and existing code. You should consistently look for opportunities to refactor and increase test coverage. You should never use “consistency” as an excuse to shirk the responsibility of taking on a necessary change.
“Consistency” properly applies to two things: principles and style. You should consistently practice the best design principles and processes available for the craft of writing software. You should consistently conform to the style of code that your team endorses. If your team likes to put curly braces on newlines instead of at the end of a line, conform. If your team likes to use Hungarian notation (shudder), conform—though you should probably try to talk them out of it (ha!). If your team consistently writes functions with 4000 lines of code, rebel. Lead by example and write short, intuitively named, expectation satisfying functions.
Rationally there can be no conflict between consistency and good design. If you pretend there is then you have accepted a contradiction. Well-designed software is “well-designed” precisely because it makes the customer happy, is easily debugged, and is easily extended with new features. “Consistency,” it is argued, makes it easier for other developers to read the code. It isn’t easier to read if the code is full of poorly named variables and functions, disorganized class hierarchies, and 4000 line functions full of stale irrelevant comments. Well-designed code is easier to read, assuming a basic knowledge of OO concepts and design patterns. Well-designed code has a logical organization and leads the developer through the task of understanding it.
I came across this post on Marlon Grech’s blog. Marlon has written a very simple wrapper for registering and unregistering INotifyPropertyChanged event handlers. I modified his solution slightly, doing caching the results of the PropertyInfo resolution instead of recalculating it every time in the PropertyChangedSubscriber.
Here is my version of the PropertyChangedSubscriber.
/// <summary> /// Shortcut to subscribe to PropertyChanged on an INotfiyPropertyChanged and executes an action when that happens /// </summary> /// <typeparam name="TSource">Must implement INotifyPropertyChanged</typeparam> /// <typeparam name="TProperty">Can be any type</typeparam> public class PropertyChangedSubscriber<TSource, TProperty> : IDisposable where TSource : class, INotifyPropertyChanged { private readonly PropertyInfo _propertyInfo; private readonly TSource _source; private Action<TSource> _onChange; public PropertyChangedSubscriber(TSource source, Expression<Func<TSource, TProperty>> property) { var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo; if (propertyInfo == null) { throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); } _propertyInfo = propertyInfo; _source = source; source.PropertyChanged += SourcePropertyChanged; } private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e) { if (IsTargetProperty(e.PropertyName)) { _onChange(sender as TSource); } } /// <summary> /// Executes the action and returns an IDisposable so that you can unregister /// </summary> /// <param name="onChanged">The action to execute</param> /// <returns>The IDisposable so that you can unregister</returns> public IDisposable Do(Action<TSource> onChanged) { _onChange = onChanged; return this; } /// <summary> /// Executes the action only once and automatically unregisters /// </summary> /// <param name="onChanged">The action to be executed</param> public void DoOnce(Action<TSource> onChanged) { Action<TSource> dispose = x => Dispose(); _onChange = (Action<TSource>)Delegate.Combine(onChanged, dispose); } private bool IsTargetProperty(string propertyName) { return _propertyInfo.Name == propertyName; } #region Implementation of IDisposable /// <summary> /// Unregisters the property /// </summary> public void Dispose() { _source.PropertyChanged -= SourcePropertyChanged; } #endregion }
I’ve been Internet-less for a few days and it’s been killing me. Internet is like coffee—it makes the world go round!
I’ve made a few updates to Yodelay that I wanted to tell you about.
First, I added an ASP .NET MVC project. I wanted to see if I could use the MVVM pattern in ASP .NET MVC. I’m not completely happy with my implementation, and the UI is kind of rough, but it works. I’ll work on cleaning it up later.
Second, I added a library for non-attribute-based validation. My problem with validation frameworks that rely on attributes is that I don’t always have access to the code for the classes I need to create business rules for. The new library uses a fluent API to configure rules for classes and properties.
Example:
ConfigureRules.ForType<BusinessObject>() .If(e => e.Id > 0) .Property(e => e.Name) .Must.Not().BeNull();
var testObject = new BusinessObject() {Id = 1, Name = "Testing" };
Rules.Enforce(testObject);
Third, I added some extension methods for the Range class which allow the developer to test for adjacent, intersecting, and disjoint ranges. Further, the range API will now find gaps in lists of ranges.
Finally, I removed the assembly signing. When I added the key files before, I password-protected them. This makes it hard for people who download the source code to compile it. I’ve removed all assembly-signing for the short term. When I”m ready to build and installation package, I’ll resign the files without password protection.