The Power and Perils of Metaprogramming

Metaprogramming is writing code that writes code. When using a third-party class, if you’ve ever said, “It would be really great to have this particular method in the class at hand,” you can use metaprogramming to make that wish come true.

Metaprogramming is not practically approachable for most developers unless the language lends itself for extension using hygienic syntax native to the language. In general, dynamically typed languages, due to their ability to dispatch method calls without rigorous type verification, offer better metaprogramming facilities than statically typed languages.

Just because a language is dynamically typed, however, does not make it automatically suitable for metaprogramming. The language has to offer specific tools for that. A good example of this is JavaScript. Even though the language has been dynamically typed from its inception, until recently it lacked the necessary tools to do full-fledged metaprogramming.

Injection vs. Synthesis

Metaprogramming comes in two flavors: injection and synthesis. The former is relatively simple and JavaScript has had this capability from the beginning. The latter is more complex and powerful, and has been possible only recently in JavaScript.

Injection is a technique where we can add, or replace, specific methods or properties into a class. The names of these members are known at code writing time. Suppose you want to know if a particular date is in a leap year. You can extract the year from the date object at hand and pass that to a utility method that may tell whether the given year is a leap year. But it would be really convenient if we could do givenDate.isInLeapYear(). The ability to make that possible, even though we don’t have access to the source code for the Date class, is metaprogramming and, more specifically, member injection.

Synthesis is more dynamic than injection—I call it adult metaprogramming—it needs more maturity and practice than injection. Suppose Employee is a class that represents employee data that’s in a database. The data that goes into a database may be highly fluid. For example, every employee may have properties like firstName, lastName, corporateCreditCard, and so on. At code writing time we may not know what properties exist. Some properties may disappear and new ones may emerge in these databases as the system evolves. However, a programmer using our Employee class may want to write something like Employee.findThoseWithFirstName(’Sara’) or Employee.findThoseWithCorporateCreditCard(’xxxx-xxxx-xxxx-1234’). In the future, when a new property named costCenter is added, the user of the Employee class may want to write Employee.findThoseWithCostCenter(...).

None of these findThose... methods may actually exist in the Employee class at any time. But when the call is made to a method that starts with findThoseWith, as a convention, then we may want to synthesize or dynamically create the code to query for data with a property whose name follows findThoseWith. That’s also metaprogramming but, more specifically, method synthesis. Examples of such approaches are the popular ActiveRecords library in Ruby on Rails and Grails Object Relational Mapping (GORM) in Grails.

Risks of Metaprogramming

Metaprogramming has the unrelenting power to alter the structure of objects and classes, so you have to be careful when using it. It can be frustrating and hard to work with code if you find several new and unclear method calls on instances of, for example, the class ‘Date‘, in arbitrary places in code because of metaprogramming. Furthermore, metaprogramming may introduce dynamic behavior, and that may introduce bugs in code. Imagine how many more bugs we could introduce by writing code that writes code—that can become a meta-problem.

Remember Voltaire’s wise words: “With great power comes great responsibility.” When using metaprogramming:

  • Reach for it sparingly and only when it is absolutely necessary. When you feel the urge to create dynamic behaviors, ask for a second opinion.

  • Do not inject or synthesize methods in arbitrary places in code. Structure the application so that developers can go to a single location—a directory, for example—where all the code related to injection and synthesis is placed. When developers find an unfamiliar method call, they can more easily locate the code that does related metaprogramming in a project that is better structured.

  • Ask for extensive, thorough code reviews. Find a colleague you respect and ask him or her to examine the code, its implications, and the use cases. Having more pairs of eyes on the code can reduce the risk.

  • Write rigorous automated tests. Tests do not prevent errors from ever occurring, but they prevent them from recurring. Good tests can improve confidence by keeping an eye on metaprogramming as the code evolves. This is one of the great ways to reduce the risks of metaprogramming.

Let’s dive into metaprogramming. We’ll see what JavaScript makes possible—you’re in for a treat.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.249.158