When magic isn’t
Discernng why certain code is considered good and other code -well- isn’t is often readily apparent by examing a handful of the code’s characteristics.
One important characteristic which is always considered is the code’s level of coupling.
Various parts of a system are “coupled” if they interact therefore depend on each other. If the interdependencies are limited to what is strictly -and practically- necessary the system is considered loosely coupled, this is good practice. The opposite extreme is considered tightly coupled, bad code smell, and to be avoided. Most code is somewhere in between.
This post is about addressing a particular case of tight coupling, one with a potential for generating run-time errors, and a language addition in C# 6 which can help with such cases, namely nameof.
Too often we find ourselves having to use a “magic string.” A magic string is a hard-coded string whose value is used to determine specific functionality. Magic Strings are a form of tight coupling; one which can typically not be validated before run-time.
Here’s a simple Magic String, “MyBackDoor”:
public User Login(string userName, byte[] password) { if (userName == "MyBackDoor") { return GetSuperAdmin(); } else { return validateLogin(userName, password) } }
A more common example are cases where the alternative is convoluted reflection. This example includes the current class, method, and method parameter identifiers (names) as part of an exception message.
void Main() { var result = NameOfClass.DivByParamMagic(1); Console.WriteLine(result); result = NameOfClass.DivByParamMagic(0); Console.WriteLine(result); } public static class NameOfClass { public static string DivByParamMagic(int paramName) { try { int result = 42 / paramName; return String.Format("The answer is {0}", result); } catch (Exception ex) { string msg = String.Format("An error occurred in the {0} method of {1} with {2} = {3}. Error message: {4}", "DivByParamMagic", // method identifier "NameOfClass", // class identifier "paramName", // method parameter identifier paramName, // value of method parameter ex.Message); throw new Exception(msg); } } } Output: "The answer is 42" "An error occurred in the DivByParamMagic method of NameOfClass with paramName = 0. Error message: Attempted to divide by zero."
This code works fine, we’ve all seen if not written similar. Nonetheless, it’s worrisome.
What happens if we refactor the method? To illustrate let’s change the class name to Math, the method to DivByParam, and the parameter paramName to something a bit more descriptive, intParam.
void Main() { var result = MyMath.DivByParam(1); Console.WriteLine(result); result = MyMath.DivByParam(0); Console.WriteLine(result); } public static class MyMath { public static string DivByParam(int intParam) { try { int result = 42 / intParam; // changed from paramName return String.Format("The answer is {0}", result); } catch (Exception ex) { string msg = String.Format("An error occurred in the {0} method of {1} with {2} = {3}. Error message: {4}", "DivByParamMagic", // method identifier "NameOfClass", // class identifier "paramName", // method parameter identifier intParam, // value of method parameter ex.Message); throw new Exception(msg); } } } Output: "The answer is 42" "An error occurred in the DivByParamMagic method of NameOfClass with paramName = 0. Error message: Attempted to divide by zero."
In order to compile we had to change two references in the method body from paramName to intParam. However, nothing clued us to the fact the magic strings no longer being sound and our error result has become misleading.
Enter nameof()
nameof() provides a refactor-safe way of getting the name of a parameter, member, or type.
Here’s our original method rewritten to use nameof():
public static string DivByParamMagic(int paramName) { try { int result = 42 / paramName; return String.Format("The answer is {0}", result); } catch (Exception ex) { return String.Format("An error occurred in the {0} method of {1} with {2} = {3}. Error message: {4}", nameof(DivByParamMagic), // method identifier nameof(NameOfClass), // class identifier nameof(paramName), // method parameter identifier paramName, // value of method parameter ex.Message); } }
After we refactor we won’t be able to compile this method until we update the three references.
public static string DivByParam(int intParam) { try { int result = 42 / intParam; // changed return String.Format("The answer is {0}", result); } catch (Exception ex) { string msg = String.Format("An error occurred in the {0} method of {1} with {2} = {3}. Error message: {4}", nameof(DivByParam), // changed from DivByParamMagic nameof(MyMath), // changed from NameOfClass nameof(intParam), // changed from paramName intParam, // changed from paramName ex.Message); throw new Exception(msg); } } Output: "The answer is 42" "An error occurred in the DivByParam method of MyMath with intParam = 0. Error message: Attempted to divide by zero."
When all is said and done nameof() provides us a simple and relatively safe way to access various program element identifiers.
The post nameof – Day 3 – VS 2015 Series appeared first on Falafel Software Blog.