Integrated Developer Environments
In this unit we discuss the interest of using an IDE for development and illustrate how key developer activities are handled using IntelliJ IDEA (ultimate edition).
Lecture upshot
IDEs gather the tools and interfaces for everyday software developer business, from code syntax highlighting, code formatting, code completion, code navigation, debugging, graphical version control, to tailored plugins for project specific requirements.
Two comments:
- On first launch, your IntelliJ IDE might look a bit different than what you see in class.
- No panic, you downloaded the right version. However, there are two UIs: Modern and Classic
- All course material refers to the Classic version
- To toggle, choose
Settings -> Appearance -> new UI
and unselect the checkbox.
- I often use key combos, to quickly access menues in my IDE.
- Key combos are provided in this guide, however, they are specific for MacOS.
- No panic, the windows/linux keys are almost the same.
- You can conveniently look up the combos on your system, using the cheatsheet:
Help -> Keyboard Shortcuts PDF
Beyond your standard text editor
It is totally possible to code even large software projects with a basic text editor. However, things get rapidly very inconvenient. For instance you do not have: syntax highlighting, auto formatting, API suggestions , code navigation, ... and many more.
And what about vim, isn't that an IDE, kind of ?
Vim and emacs are editors, written by developers, for developers. You can totally use them and configure a long list of plugins to obtain something IDE like. However you might end up spending more time configuring your editor, than on your porject. Ok for power users, but in this course we'll stick to a graphical IDE.
Syntax highlighting
A key feature of any IDE is language specific syntax highlighting.
- Java programs must be 100% syntactically correct, otherwise the compiler will reject your code.
- It is too easy to make a little syntax mistake, your program could e.g. be rejected just because you miss a semicolon:
Spot the syntax error: System.out.printl("Hello, World!")
Actually there are 2:
- Correct method call is print
or println
.
- Missing semicolon after statement.
- Even the most experienced developer makes syntax errors.
- That is why we use assistive tools:
- IDEs have automatic AST analysis to highlight all syntax errors, while you are typing.
- A red marker on the scrollbar indicates a syntax error.
- Hover on the marker or the line to obtain an explanation of the error, e.g.: "Missing semicolon after statement in line 42."
- In some cases, the IDE even suggests a quick fix, which you can apply by a simple click.
Auto code formatting
- Formatting goes beyond syntactical correctness.
- From a compiler's perspective, the two below code snippets are identical, they share the same AST structure:
- Unformatted code:
- Formatted code:
- Most humans would agree that the second sample is easier readable.
- IntelliJ can auto-format code for easier readability
- Mac OS:
Command (⌘) + Option (⌥) + L
- Linux / Windows:
Ctrl + Alt + L
- Mac OS:
Formatting profiles
There are infinite ways to format code
- Examples:
- Some developers prefer
tabs
overwhitespaces
- Some developers want all
{
s on the next line - Some developers want methods to be indented only
2
spaces - ...
- Some developers prefer
- It's important that all developers use the same formatting settings
- Code formatting will soon become a grading criteria
Checkstyle as formatting profile
We'll use a formatting profile, that is at once compatible with checkstyle, a static code linter plugin. (There will be a dedicated lecture on linters)
- Install the Checkstyle plugin
- Go to
Settings -> Plugins -> Marketplace
- Type "Checkstyle"
- Activate "Checkstyle plugin"
- Go to
- Activate the Google Checkstyle profile
- Download the Google Checkstyle configuration xml file
- Go to
Settings -> Editor -> Code Style -> Java
- Click the cog-icon
- Select the
google_checks.xml
file
Once the profile loaded, all code you format automatically complies to the google code style conventions.
What's the effect on git commits when different formatting profiles are used?
Almost every line of your code changes in every commit. It will be close to impossible to distinguish code changes from reformatting changes. Merging code will be tedious and time consuming.
Navigation
Other than a standard text editor, the IDE has a notion of how code is connected.
- This allows you convenient navigation, e.g. to follow call graphs.
- To follow a called method, to its definition, hold
Command (⌘)
and click on the method call.
Inspect file structure
Regardless of whether you're writing java or Markdown code, there's always some sort of structure.
- Of course you can scroll until you find what you are looking for
- But the file structure combo is faster:
Command (⌘)
+F12
(+Fn
on laptops)
Structured search
- Imagine you know about a field that stores some specific information, but you cannot remember in which class it resides.
- IntelliJ lets you conveniently search for specific code elements, e.g. fields of all classes.
- Use:
Shift
+Command (⌘)
+A
=> actions - Then type:
Structure
- Use:
-
You'll see a search template, for all fields of all classes
* But you can easily delimit search, e.g. to scope search tofinal
fields:
Filters
Often times the amount of results is more than you can consume. But you can restrict the output to exactly what you are looking for.
- You might e.g. be searching for a print line statement, but it could be anywhere in the code.
- To do so you can start by searching for all method calls (which will include the prints)
- Next you can apply a filter (filter symbol on the right), to reduce the result set to specific criteria.
- The filter can apply to the parameter type, or the method name itself. Just click on the element for you want to
apply the filter, before adding the filter with the
+
icon.
- The filter can apply to the parameter type, or the method name itself. Just click on the element for you want to
apply the filter, before adding the filter with the
- Example:
- click
MethodCall
and add a text filter:print
. This reveals all methodsprint
- To match also
println
, use a regex, i.e.print.*
- click
- Now you see all print statements, regardless of
print
,printf
, orprintln
.
Browse the provided template searches and filters
The search statement syntax is sometimes a bit tough to figure out, but IntelliJ comes with templates for most common searches. Don't bother reinventing the wheel when you can just pick an off-the-shelf serach template.
Advanced: You can also replace structurally, e.g. to replace all
System.out.println
statements consuming a string by a logger call.
Refactoring
Advanced renaming
- Manual renaming is pretty unsafe:
- You have to rename all variable / method occurrences.
- If you forget a single occurrence, your code does not compile any more.
- If you rename a variable, you should also...
- adapt getters and setters.
- adapt the JavaDoc.
- If you rename a packages, many files may be affected, and be require moving around on disk.
- Your IDE can help you with the task, because it performs a static code analysis, and knows how identifiers are
connected across your code (and your files).
- Select the identifier to rename
- Access the rename feature with
Shift + F6
(May requireFn
key on laptops, to accessF6
) - Type a new name
- Hit enter
Careful with package renaming when a VCS is involved
Git might not understand the files have just been renamed and moved around, and future merges might become a hassle. For package renaming, it's still a good idea to use the git mv ...
Command (⌘)s.
Changing signatures
Similar to renaming is modifying the signature of a method, e.g. to adding an input parameter.
- You have to consistently modify all method calls, or your code won't compile.
- IntelliJ can once more help you with the job:
- Change only the signature definition (now your code has errors)
- Click the
@R
annotation on the left side-pane - Add a default value for the new parameter, in all existing method calls
- Alternatively you can use the dedicated method signature refactor dialogue with
Command (⌘) + F6
(andFn
on laptops). The dialogue has extra features, e.g.:- Change order of parameters
- Method overloads
Extract method
- Often you have a few lines of code that perfectly do a job, but you'd like to have them accessible as a dedicated
method (and that method simply being called where the code currently is).
- Example:
/**
* Creates an interleaved true, false, true, false... array and prints the outcome.
* @param length as the total amount of values to be placed in the result array.
*/
void foo(int length) {
// initialize a new all-false boolean array
boolean[] myArray = new boolean[length];
// convert every value at an even position to true:
for (int i = 0; i < myArray.length; i++) {
if (i % 2 == 0) {
myArray[i] = true;
}
}
// print the array content
for (int i = 0; i < myArray.length; i++) {
System.out.println(myArray[i]);
}
}
- Could be refactored to:
/**
* Creates an interleaved true, false, true, false... array and prints the outcome.
* @param length as the total amount of values to be placed in the result array.
*/
void foo(int length) {
// initialize a new all-false boolean array
boolean[] myArray = new boolean[length];
setEveryEvenPositionTrue(myArray);
// print the array content
for (int i = 0; i < myArray.length; i++) {
System.out.println(myArray[i]);
}
}
/**
* Sets every even position of a provided array to true.
* @param myArray the array to be modified.
*/
private static void setEveryEvenPositionTrue(boolean[] myArray) {
// convert every value at an even position to true:
for (int i = 0; i < myArray.length; i++) {
if (i % 2 == 0) {
myArray[i] = true;
}
}
}
- You could manually edit the code to create a new method, copy&paste the selected lines, and then insert a method call in the original location...
- Or you can just use an IntelliJ shortcut:
Option (⌥) + Command (⌘) + M
There is also an inverse operation: Inline method. The reverse replaces a call to a method, by the method's code. Highlight the method call and hit
Option (⌥)+ Command + N
.
Magic values to constants
- Hard coding magic values is considered bad practice, even if the variable appears only once.
- Select the variable and hit:
Option (⌥) + Command (⌘) + C
Fields to method parameters
- Often you have values in a method that should be injected from outside.
- You can mark the parameters and hit
Option (⌥) + Command (⌘) + P
- Example:
/**
* Creates an array and calls toto on it.
* @param length as the size of the array to create before passing it to toto.
*/
void foo(int length) {
// initialize a new all-false boolean array
boolean[] myArray = new boolean[length];
toto(myArray);
}
- Converts to:
/**
* Receives an array and calls toto on it.
* @param myArray as the array to call toto on.
*/
void foo(boolean[] myArray) {
toto(myArray);
}
Expression to variable
- Longer expressions can be hard to understand:
-
You can replace parts of the expressions by named variables with
Option (⌥) + Command (⌘) + v
:
Code completion
Basic completion
If you are about to write a function that returns a boolean
, IntelliJ can match the return t...
String and propose
your Option (⌥)s:
_IntelliJ can auto-format code for easier readability
- Mac OS:
Control (⌃) + Space
- Linux / Windows:
Control (⌃) + Space
Method call completion
You can also use the IDE to browse available method calls, e.g. when you prepared for method call with:
Of course, you can always just read the official API, but a quick lookup of available methods is usually faster, and reduces context switches.
Live Templates
Often times you want to create a pretty standard construct, that always looks the same, such as a main class
declaration, or System.out.println(...)
statement.
- IntelliJ offers templates, that is short abbreviations that expand to complete code constructs when typed:
- Examples:
sout
: expands topsvm
expands tofori
expands to:
More live templates
For a full list of templates, check Settings -> Editor -> Live templates -> Java.
Generated methods
Besides modifying existing code, IntelliJ's refactoring Option (⌥) also lets you generate off-the-shelf code, for example
the equals
method.
- The
equals
method is required to compare if different objects are semantically equivalent. -
Example:
-
Writing an
equals
method is mostly boilerplate code, the only relevant detail is which object fields should be considered for comparison. Command + N
lets you open a popup menu, to select which fields should be used for the generatedequals
method.
Run profiles
- Run profiles define how your code is compiled and started.
-
Intuitively one might think this is trivial, as there are inviting green triangles ("
▶ ") all over the editor. -
However, by default the IDE knows nothing about a build system we might use.
Careful with the default run configurations
The default run configurations are for minimal java projects, i.e. projects without parameters, and without dependencies. They do not trigger maven, using the green triangles you will not benefit of any build system advantages.
Before we can develop on a maven project, we need to tell the IDE what our intentions are:
- What build system configuration to use
- What should happen when we "run" our project
Loading the build system configuration
- IntelliJ is often good at taking a fair guess from files found in a project.
- However, it is more reliable to tell IntelliJ explicitly about the project type.
- As you remember from a previous lecture, additional libraries can be configured in mavens
pom.xml
(for example JUnit) - Maven modifies the classpath for us, we can use these dependencies
- If IntelliJ does not know about maven, the classpath goes unchanged and we'll see plenty of errors, whenever we attempt to work with a JDK external artifact.
- As you remember from a previous lecture, additional libraries can be configured in mavens
- To explicitly tell IntelliJ about the Maven build system:
- Right-click on the
pom.xml
file on the left - Select
Add as Maven project -> Reload
- Right-click on the
Configuring a Maven launch configuration
-
We can tell IntelliJ it should use Maven when the top "
▶ " is clicked.- Click the dropdown (next to the "
▶ " icon) - Select "Edit configurations..."
- Click the "+" icon, then select the "Maven" Option (⌥):
- Give your configuration a name and specify the maven Command (⌘) to execute:
- For the Run field, omit the initial "mvn" statement.
- Example, if you want to call
mvn clean package
, whenever "▶ " is clicked, then just typeclean package
- Click the dropdown (next to the "
-
From here on you can run your program with maven, using the top launcher.
- You can also repeat the last launcher call with:
Control (⌃)+ R
Define multiple run configurations
Define multiple run configurations for various maven commands, e.g. one for building a jar, one for only running tests, one for executing your code, etc. We'll soon learn how to efficiently use the maven Command (⌘) line Option (⌥)s to run specific phases of the build system.
Debugging
In essence, debugging translates to: "Freeze your program at execution and take all the time you need to inspect its state."
Debugger usecases
Novice programmers often search bugs by adding print statements to the code, i.e. the source code is modified and then we inspect the textual output. However, there are several inconveniences:
- You only have the information printed (and the information you need might not be contained)
- There might be a lot of output, and it might pile up fast
- Every time you want to adapt you need to modify source code, recompile, provoque the bug again. This may be time-consuming.
- When you're done you have to remove your print statements, or future bug hunt will be obscured by existing prints. A lot better is the built-in IDE debugger.
- The debugger does something really magical: Provide you the illusion you were executing source code
- Reminder: That is not the case, java is a compiled language, your source is code never executed !
- This is extremely helpful, because you can stop at any line in the code, and then instruction by instruction inspect what happens at execution. The debugger has some neat feats:
- Access to all variables, at any point in time
- Access to the entire call stack.
- You can modify state (Change variable state)
- You can set conditions, that fast-forward execution to specific situations.
Debuggers are not just for debugging
Slow, step by step execution is also very useful to understand huge codebases. If you join a project and you have no clue which code is actually relevant to a feature, just launch the debugger and trace which methods are called.
Basic breakpoints
Basic breakpoints are the most essential debugger feature. A breakpoint tells the program to stop at a given line.
- Click the line where you want your program to stop.
- Click the bug symbol (next to the green triangle).
- Your program will stall at the selected line (if the line is reachable !)
Some breakpoints aren't round, but displayed as diamond symbol. These are lines without statemented, e.g. method definitions. Your program can never stall there. It's best to remove them again right away.
Variables
Breakpoints are pretty useless without also inspecting program state, as your program is stalled. You want to see what goes in your variables. There are two ways to inspect variable state:
- Hover over a variable: The value appears in a popup annotation
- Open the variables tab: This one is useful for object variables, because most of the time you want to inspect their inner state, not just see the object reference.
Right click to modify
The variables tab also lets you modify variable state. You can e.g. fast-forward to the 100th loop execution, but changing the loop counter. Just right-click on a value in the variable tab and chose set value.
Step by step navigation
Most of the time a bug appears over a series of many instructions. To find it you have to step by step retrace execution and inspect how variable values change (and find where things go wrong). The IntelliJ debugger has just the right buttons for that kind of navigation:
Step over
: brings you to the next statement.Step into
: brings you inside the current method call.Step out
: brings you to the calling method (see call stack displayed on bottom left)Step to cursor
: brings you to where your cursor carret is.
Use force touch (on Mac)
On MacOS you can force touch on a line, to quick access Step to cursor. The debugger then fast forwards to where you clicked.
Expressions and watches
- Sometimes you want to quickly evaluate a complex expression, rather than query a single variable state.
- Of course you could change the source code to introduce an intermediate variable, but we've already argues why changing the source code for debugging is not optimal.
- Luckily the debugger has your back on this one too:
- Click the calculator symbol, and puch in your expression:
- It uses standard java syntax, so if you have an
int
variable foo, you can just punch infoo * 42
to compute the immediate result.
- It uses standard java syntax, so if you have an
- You can also hold the
Option (⌥)
key and hover over a more complex expression, existing in your code, e.g.foo * 42 + 8
- An annotation popup will appear to tell you about the expression value.
- This is particularly useful for return statements that compute an outcome without extra intermediate result variable.
- Finally you often want to trace the value of an expression as your program execution proceeds
- This is possible with
Watches
- Think of a watch like an additional variable, that is based on variables in the source code, but only lives in the debugger
- To add a watch: just click the
+
symbol on the bottom left and enter the expression to compute your watch. - As you your program execution continues, your watch lives in the variable tab, next to all "real" variables and you can trace it's value.
Use drag and drop
You do not need to create watches from scratch, you can also just drop existing variables into the watches tap and alter their expression definition.
Conditional breakpoints
- Finally, often a bug is not associated with a specific line of code, but rather with a specific variable value.
- That is, the breakpoint might be hit hundreds of times, before you actually reach the state that you want to inspect.
- Think of a for loop, where the bug only appears after 100 interations
- You do not want to click the "continue" button 100 times...
- Luckily breakpoints can be "upgraded" to conditional breakpoints, that is breakpoints that only stall execution if a certain condition is met.
- Example: you can chose as condition, that the loop counter must be greater 100.
- To turn a breakpoint into a conditional breakpoint, just right-click its red circle and choose
set condition
- The condition works again like java code, you can write e.g.
loopCounter >= 100
Don't delete, disable
Disable condition breakpoints when you don't need them any more. Setting the right conditions takes some time, and you might need your breakpoint again. Don't delete it, just right click and disable your breakpoint. You can re-activate it later, when you need it again.
Bonus: Dependency matrix
- IDEs do not just deal with individual lines of code, but can also provide you with insights regarding macro-structures of your code.
- An example is the dependency matrix:
- Each line and column represents a class, each cell tells how many times a class calls or references it's counterpart.
- The matrix is automatically sorted bottom up, i.e. classes at the bottom are the ones needed most, classes to the top are the ones needed least.
- The matrix also gives some insight on suspicious constructs, i.e. cyclic references, which are represented as red squares.
Use the matrix to verify architectural patterns, e.g. MVC
The dependency matrix is an efficient way to analyze whether a project respects the MVC pattern: The Model only deals with data, and must not have any calls to Controller (logic) or representation (View). Model package must be the lowest classes in the matrix. The Controller changes model state, but does not care about representation. It has fewer usages than the model, but more than the view (no-one calls the view). The controller package's classes must be located between model and view in the matrix.
Literature
Inspiration and further reads for the curious minds:
- The 15 most usefull IntelliJ shortcuts
- IntelliJ key combo cheatsheet
- Jetbrains java tutorials
- Jetbrains IDE trainer
- IntelliJ debugging IntelliJ refactoring
Key combos
Usage | Key Combo |
---|---|
Search all files | Double shift |
Recent code file | Command + E |
Action | Shift + Command + A |
Reformat code | Command shift + L |
Show file structure | Command + F12 (+ Fn on laptops) |
Follow method call | Command + Click on method |
Key combos
Download and print the key combo reference chart, highlight and memorize the most common key combos. It will significantly speed up your workflows.