Readability/Maintainability
This page focuses on creating MPS code that is easy to read but also deals with the topic of maintainability. Too often, code is written by one person but has to be maintained by someone else. Understanding the mindset of other people can be difficult, especially if they don't follow any patterns and the code is (visually) hard to read.
Maintaining code means dealing with technical debt, reducing it, and cleaning up the codebase periodically. That also means that sometimes code must be deprecated and removed after some time. Or it must be rewritten when it uses deprecated code, for example, after upgrading to a new MPS version. Neglecting those tasks makes maintaining the code more difficult in the future.
Readability¶
Establishing a coding style is essential for Base Language code and other parts of your code, such as the language aspects. When the code is consistent, it is also easier for other people to understand because they can see patterns in the code. For writing names, read: Write short code with good names | Sandi Metz's Rules for OOP. Read the MPS Java style guide ⧉ for more ideas. In addition to consistency, short code helps readability.
Learning to write readable code takes time and must be practiced. Coding standards can help enforce specific patterns, but sometimes code reviews are necessary so that others can help improve your code.
- Simplicity: You should strive to make your code as simple as possible. Unnecessary complexity will make the code hard to understand and prone to errors. The code can be clever if it focuses on readability and maintainability. If the code is kept short, it is easier to go through, but if it's too smart, it will take too much time to understand and edit.
- Use the same naming scheme for concepts (e.g., start interfaces with the letter "I"), intentions, or actions. Always place code in behavior methods or utility classes with similar naming patterns. Follow standard naming conventions such as camelCase for variables.
- Name temporary variables such as counter variables consistently. Read this StackOverflow answer ⧉ for some ideas on how to name variables.
-
Create methods if the code becomes too long and avoid boilerplate code. For example: Use setters and getters for classes:
A property can be created by typingproperty
at the same place where fields are declared. -
Extract long, boolean expressions that you use as conditions into variables. For example:
Split up boolean expressions into variables:You can reduce conditions by saving them in closures or functions. For example: If statements can also improve readability when you use them as guard clauses, also known as assert or precondition. Compare the following snippets: The first one doesn't use guard clauses.The second snippet uses them and is more readable. -
Functional interfaces accept lambdas ⧉ as parameters. For example, a thread that accepts a Runnable object can also be called like this:
-
Use enhanced for loops instead of for loops with counters. For example:
The second statement doesn't even need a type for the variable. For lists, other ways exist:list.forEach({it => })
for iterating the list andlist.select({it => <no statements> })
orlist.selectMany({it => <no statements> })
for transforming the list. For adding many elements to a list, uselist.addAll(elements)
. -
Use empty lines between statements for readability (StackOverflow discussion). When a line becomes too long, you can use a few Base Language refactorings ⧉, such as introducing variables or extracting code into a method. You might also want to experiment with the default text width of the editor (preferences → Editor → MPS Editor), which is set to 120 by default. This value is relatively low and can be increased depending on your monitor size.
- Use virtual packages to organize your code. How you arrange the modules is up to you. Some suggestions:
- Arrange type of module: lang, test, build, etc.
- Arrange by a group of languages: tables, tooltips, widgets… or core, expr, trace, etc.
- Arrange by language maturity: stable, staging, rest, etc.
- Arrange by a numbering system + other criteria: _10_build, _50_active, _60_demo, _70_attic, etc.
- Avoid double negation as they are hard to understand. Also, use a positive tone to help with understanding statements more intuitively. Example:
- Avoid deep nesting like a chain of calls (
nodea.nodeb.nodec.property
). Consider providing a method to access deeper nested nodes or directly jump to the nested node. For example: The nesting is also relevant for if conditions, loops, and nested method calls. Keep them flat, or use only a few levels of nesting. - Four-parameter rule: a method accepting many parameters is hard to follow. A good maximal number of parameters is four. Refactor methods into smaller methods. You can use MPS builders ⧉ to avoid many parameters.
-
Group your code: Group the fields and methods of Base Language classes by their visibility and purpose. For example, group the private fields and methods, and group the public fields and methods together. Also, group the fields and methods related to each other by their functionality or logic. Also, break down big tasks into smaller chunks. You can split up long (behavior) methods into multiple methods. You can place helper methods as static methods into separate classes. Call them more easiliy using Extension methods ⧉ For example:
Group code into multiple root nodes, models, or modules. If the generation is across multiple models, cross-model generation ⧉ applies.
Maintainability¶
- Mark classes/concepts etc., as deprecated when you don't want others to use them, and you will remove them in the future. When you add a deprecation date, you can use it as a reminder to remove deprecated code in regular intervals or enforce it through a linter ⧉.
- Use the text TODO, FIX, or FIXME in the comments. The TODO tool can find those strings and the commit dialog when the option Check TODO (all) is activated. Other texts not supported by the tool but common are NOTE, XXX, HACK, and BUG ⧉.
- Don't repeat code. You can write the same code twice but consider creating a method when you duplicate the code again. Repeated code breaks the reading flow and makes updating the code more complex.
- Delete unnecessary and unused code. If you use version control and need to return to the old code, you can still find it in the history.
- Use comments to document complex code. Don't use them if the method signature contains all the necessary information. You don't have to place every piece of information next to the code. You can use documentation, such as this website, or documentation created with com.mbeddr.doc (+ optional com.mbeddr.aspect) can be used not only for user documentation but also for technical guides for developers.
- Avoid using magic numbers which are direct usages of numbers in the code. Example:
Stack Exchange: Software Engineering¶ ⧉
- What's wrong with comments that explain complex code? ⧉
- When is a BIG Rewrite the answer? ⧉
- How can I convince management to deal with technical debt? ⧉
- How do I know how reusable my methods should be? ⧉