Skip to content

Index

This document lists some recommendations for code written in MPS. For a general introduction to code smells, go to Code Smells | Coding Horror and How To Write Unmaintainable Code. Clean code has many benefits. Clean Code: Writing maintainable, readable, and testable code | https://blogs.sap.com/ ⧉ describes those benefits.

Implementation

  • Prefer composition over inheritance ⧉ (Specific Languages' blog)
  • Implement INamedConcept ⧉ in concepts that should have a name. The name property will be returned when you call node.getName(). MPS uses the name in editor tabs, the logical view, as the default presentation for references and other places.
  • Use isInstanceOf ⧉ to check if a node is an instance of a concept instead of using the Java instanceof operator, for example, node.isInstanceOf(IfStatement). Alternatively, use the ifInstanceOf statement to save the converted expression in a variable directly. For example:

    1
    2
    3
     ifInstanceOf (node is IfStatement ifStatement) {
    
           }
    

  • Use the built-in collection classes such as collections, set, list, and map (documentation ⧉). Null-safe languages implement those classes. For example:

    list<string> listOfStrings = new linkedlist<string>(copy: new string[]{"ab", "ba", "aaaa"}); 
    #print listOfStrings.where({it => it.startsWith("a"); }).join(",");
    
    For lists of nodes, there is the particular list type nlist, for example, nlist<ClassConcept> classes = new nlist<ClassConcept>; Choose the right collection ⧉ for your task.

  • Use the access language instead of directly calling ModelAccess ⧉ methods. For example:
    1
    2
    3
    4
    node<ClassConcept> classNode = new node<ClassConcept>(); 
    command with #project.getRepository() {
      classNode.member.add(<argument>);
    }
    
  • Write tests for new features that you have developed and bugs that you have fixed. Always test generators, sometimes also the type system. You can even start by writing the tests first (test-driven-development ⧉).
  • Don't try to use features in MPS at places that don't officially support them. Not supported means that the official documentation doesn't mention it, and you can access the feature only through hacks and workarounds. Examples:
    • invoking the generator in type system rules
    • UI code in the behavior aspect
    • accessing the project instance in unsupported places, for example, the generator
  • Avoid strict node casts ⧉ (Specific Languages' blog)
  • Use Java Streams to Write Clean Java Code ⧉. Warning: While streams can be helpful, consider using the MPS collections language ⧉ for most cases. It is better understood by MPS developers and easier to use. Switch to Java streams only when the collections language is not powerful enough.
  • Use logging for troubleshooting purposes. Remove logging statements from production code or reduce the log level to debug. Do not output log messages when they are not necessary.

Null Safety

  • Use the language checkedDots for saver access of possible null values. You can check nodes for null: node.isNull ⧉
  • Use the annotations @NotNull and @Nullable for the Base Language code. A specific type system rule in MPS checks these annotations. The annotations are also enforced at runtime by the compiler.
  • Use :eq: (NPE safe equals operation) and :ne:(NPE safe not equals operation) instead of == ⧉ and equals.
  • You can still return optional values instead of null in Base Language code (Java explanation ⧉).

Some languages, such as the smodel and collections ⧉ languages, are also null-safe. The following code will correctly return false:

node<ClassConcept> n = null;
#print n.member.first as IfStatement.forceOneLine;

MPS also has a null analyzer for Java that is part of the data flow analysis (nullable analyzer, short explanation of how it works ⧉). The type system checks it, and you get a warning when, for example, a method call might throw a NullPointerException.

Exceptions and Warnings

  • Use checked exceptions (e.g., IOException) for recoverable conditions and runtime exceptions (e.g., NullPointerException) for programming errors (Checked versus unchecked exceptions ⧉).
  • Consider alternative ways of showing errors than throwing exceptions, such as showing notifications ⧉. Examples of such notifications are balloons and dialogs.
  • Attach exceptions to log statements. The stack trace will print in the log file if it is a low-level log statement (e.g., log error). For ordinary log statements (e.g., message log error), you can open the stack trace through the right-click menu in the messages view. Example:
    log error "This is an error", myException
    
  • Clean up in the finally-block of a try statement. The try-with-resources ⧉ statement is also supported. An example of cleaning up could be a temporary model only needed in a test. When the test fails, the model still needs to be disposed of. To tie an object to the lifecycle of a parent, use the classes Disposer and Disposable ⧉ from the IntelliJ platform.
  • Treat Warnings As Errors ⧉, and also don't ignore warnings/errors. Warnings might become errors or even make the compilation fail in the future. For example, the repository parameter in the access language (e.g., read action with global repository {}) was optional in the past but later became mandatory because using the global repository is discouraged. Ignoring too many messages can also hide real errors you must address. For example, a warning that a NullPointerException can happen might reveal that not tested situations exist where an object is null and will crash something. That can happen, for example, when code is executed on the command line instead of inside MPS, where it can never fail.
  • Warnings should not replace documentation ⧉ (Specific Languages' blog)

Miscellaneous

  • Be careful when using Unicode characters inside MPS. It might break something (MPS-33687, MPS-31835 ⧉).
  • Use Gradle for creating build scripts (minimal example). They are better supported (e.g., mps-gradle-plugin). They can express the build logic more cleanly through Groovy or Kotlin than other solutions such as Maven or Ant (article comparing all three solutions ⧉). While the MPS build language generates Ant scripts, you can abstract away this layer nicely with Gradle + the MPS Gradle Plugin. For example:

    1
    2
    3
      task buildLanguages(type: BuildLanguages, dependsOn: prebuild) {
      script "$buildDir/scripts/build-languages.xml"
      }
    
    Most MPS projects that used Maven in the past were migrated to Gradle.

  • Do not leave debug statements in production code, including MPS log statements and System.out statements. Especially in tests, outputs can not only be annoying but can also fail the test with an UncleanTestExecutionException The only acceptable debug statements are those that have the log level set to debug or lower because they are not visible by default.


Last update: July 16, 2023

Comments