In our special Java Daily edition, we’d like to introduce you to Benjamin Muskalla. He was kind enough to share his experience on 10 Java-related questions.
Benjamin (@bmuskalla) has been following his passion of building tools for improving developer productivity. Currently, he is working on securing the open source ecosystem at GitHub, specifically on GitHub’s CodeQL security analysis technology. In a previous life, he has been an active committer of the world-class Eclipse IDE, a JUnit Christmas Decoration Extensions and was a core committer on the Gradle Build Tool. TDD and API design are dear to his heart as well as working on open source software.
Java Daily: In a blog of yours, you mention the “Simplify Protocol” refactoring. Can you give us a quick summary? Is there a case where you wouldn’t recommend using it?
Benjamin Muskalla: The overall goal of “Simplify Protocol” is to reduce the dependencies of a unit (method, class, constructor). The refactoring is meant to help you make more appropriate patterns visible and reduce incoming dependencies. The first step is to refactor away from the parameter object pattern. Given a unit under test, look at all the inputs (constructor arguments, method parameters) and expand them to the most primitive types available. Try to deliberately avoid parameter objects and use the most fundamental parameter types possible (mostly built-in or custom value types). This usually leads to an explosion of parameters. Once done, the real work starts. Identify actual groups of inputs and combine them (parameter object) or keep them as primitives. This helps to cut dependencies to their minimum and prefer properly defined inputs over generic god-class parameter objects (👋 “Context” classes). This technique is most useful for code living in the core domain of your application and may be less worthwhile for framework-specific glue-code that may need to adhere to certain interface definitions.
Java Daily: Testing is an essential part of the development process, and clean code is mainstream in today’s standards. However, many companies, which have older tests, where this simplicity is absent, refuse to refactor them just because “they are valid” – what’s your opinion on that?
Benjamin Muskalla: As with everything, “it depends” ™. It depends on how the tests are used as a safety net for the application, how much the application code is changing, and which phase of its lifecycle the application is in. If the code under test is still in active development, not investing in the simplicity of the code and the interaction with tests usually leads to a significant slow down of new features as it becomes harder and harder to write tests for new code. This inevitably leads to engineers taking shortcuts, not writing tests, and this is a downwards spiral leading to more code rot. It must be easy for engineers to do the right thing if they want to move the needle.
On the other hand, if a project is in pure maintenance mode (critical bug fixes only), this effort may not be worth it. When you only invest in more minor fixes and don’t intend to significantly change the architecture or design, rewriting tests in those cases is actually a risk that needs assessment whether it out-ways the cost. Michael C. Feathers’ book “Working Effectively With Legacy Code” has many patterns on approaching a codebase without starting over.
Java Daily: Integration tests vs. unit tests – what’s your favourite and is there a type that you dislike?
Benjamin Muskalla: For a maintainable codebase, both types (and a few more) are essential. For the fun of it, let’s say I have to choose one of them. Depending on the project, my answer would vary significantly. If I’d be tasked to work on a green-field project with a good team, I’d always go for unit tests. They provide the tightest safety net with the fastest feedback cycle. But it’s only an actual safety net if tests capture the application’s behaviour and not just replicate the production code line by line (which quickly happens if your test relies too much on mock verification). At the same time, if you ask me about taking over a legacy code base, I may favour picking integration tests. While they may be slower and capture less specific behaviour, it allows for faster renovation (compared to poorly written unit tests). The good thing is, you don’t have to pick. But you may choose a focus that helps balance risk and value given your specific circumstances.
Java Daily: Regarding the unit tests – many people would rather make a specific method throw an exception instead of going through the whole method when they are testing functionality in the first lines. Would you consider this a “good practice”?
Benjamin Muskalla: If you mean explicitly setting up a test to run into an exception to avoid a proper setup that would allow the method to run to the end, I think that’s a pretty bad practice. Given a particular setup, you may test for specific exceptions. That’s good; ensure your preconditions are tested. But if you try to test any actual functionality, you should not let exceptions control the program flow. If it is hard to set up your SUT, consider splitting it or simplifying its protocol to make it testable.
Java Daily: There are a couple of blogs in which using InjectMocks in tests is not preferred, and the suggestion is to use a constructor instead. What’s your opinion on this?
Benjamin Muskalla: I think this is up to the team working on the codebase and their current usage of mocks. If your domain model is so complex that you can barely instantiate things on their own without mocking, @InjectMocks might be the syntax sugar you want in order to postpone fixing the problem. I personally prefer to explicitly call out mocks if I need them, passing them explicitly into the constructors of the SUT. Though I believe that is a choice everyone has to make on their own.
Java Daily: Many IDEs and tools have become way smarter than before. Do you think people rely too much on the help of the IDE? What developer tools do you use?
Benjamin Muskalla: One of the most rewarding aspects of being a software engineer is that our job allows us to regularly get into the state of flow (or “the zone”, as some may call it). I’d like to quote the psychologist Mihaly Csikszentmihalyi who’s recognised and named the concept of flow:
“Enjoyment appears at the boundary between boredom and anxiety when the challenges are just balanced with the person’s capacity to act.”
― Mihaly Csikszentmihalyi
Getting into the flow is very specific to each and every person. It depends on your skills challenged by the tasks you’re working on. If you’re still new to a language, you may still fight basic syntax – whereas the help of an IDE comes in very handy. And if you’re very fluent with a language and exactly know what you’re doing, the IDE helps you automate the more mundane tasks to keep you in the flow. All skill levels benefit from the automation an IDE provides as it allows them to stay focused on the problem.
Java Daily: Nowadays, some IDEs suggest code changes, which can reduce the number of lines of code but makes the code itself not that readable. If people have not read about the “speed reading” that you mentioned in a blog, they would just accept whatever the IDE suggests to them. What’s your opinion on this – should all methods be shortened?
Benjamin Muskalla: Being able to speed read code is just one driver for a more general goal: writing intention-revealing code. The compiler will accept any code (as long as syntactically correct). The difference is whether an engineer immediately understands the intention of some of the code or struggles for hours to understand it. And I believe (at least today) that this is where the IDE meets the cognitive abilities of an engineer. An IDE can help to quickly refactor code the one or other way (e.g., if/else <-> switch, extract/inline method, …) but will only do the mechanical parts. It takes an engineer who understands the intention to drive the IDE towards making the code readable – so the code reveals its intent. As the Zen of Github describes, one of the basic answers to “why did you do X?” is “Approachable is better than simple.”. A great book on the topic was recently published by Felienne Hermans: The Programmer’s Brain.
Java Daily: Are the features, which Java provides regarding annotations, functions, and some IDEs autocomplete, creating more confusion among the people who start learning Java as they won’t see what’s actually “behind”?
Benjamin Muskalla: No matter the language or technology, we as engineers have to deal with an inherent complexity (and often accidentally complexity). I believe engineers with a growth mindset make it one of their core skills to work with technologies using basic assumptions while learning about bits and pieces on the way. Nowadays, you can’t sit down, read a book and know everything there is to know about a specific technology. A book may very well be your preferred way of getting an overview before you start to dive into the hands-on work yourself. You’ll have to learn how to learn. And that requires sometimes making assumptions or leaving gaps until you can learn more of the details (see also How Developers Stop Learning). Most senior engineers I’ve met also had the desire to build their own XYZ, which is suppressed mainly nowadays because of the never-ending stream of available libraries. But that should not stop you. Does the Java ecosystem need another dependency injection framework? I don’t know. But is it a great learning opportunity for someone to try and build a minimal one? Most definitely. Do you need to publish it? No. But it will be a huge opportunity to learn about the choices, edge cases, and technical challenges along the way to be way better prepared to compare existing technologies for their fit.
Java Daily: And as a follow-up to the previous question – do you think people with no experience should start with an older version of Java and slowly increase towards the latest or directly adapt to the latest?
Benjamin Muskalla: Given Java’s backward compatibility guarantees, there is no point in using an older Java version. In fact, it would actually be a step backward. The most recent version gives you all the incredible enhancements the JDK team has been working on over the last months and years. Which ones you start learning about or which ones you put on the backburner is entirely up to you and heavily depends on your context and skills from other languages. Someone with a functional background might pick up Streams a lot easier than someone with pre-Java8 knowledge did when they were released. The same goes for newer features like “pattern matching for switch”. If you’ve never seen the “old way”, there is no harm done. You don’t need to learn a language in the same order as the language evolved syntactically.
Java Daily: You said you had just joined Github, working on CodeQL. What’s your initial impression of it?
Benjamin Muskalla: CodeQL lets you query code as though it were data. Security vulnerabilities, bugs, and other errors are modeled as queries that can be executed against your code. You can run the standard CodeQL queries, written by GitHub researchers and community contributors, or write your own to use in custom analyses. Given the deep integration with languages like Java or Python, CodeQL allows you to write queries that operate on simple abstract syntax tree representations and enable you to traverse the data flow and the control flow graph. It’s an incredible team of people on the mission to inspire and encourage the community to secure the open-source software we all depend on.
Is there anything else you would like to ask Benjamin Muskalla? What is your opinion on the questions asked? Who would you like to see featured next? Let’s give back to the Java community together!