Skip to content

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:

  1. 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.
  2. 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:
      package ca.uqam.info;public class App{public static void main(String[] args){String uni = "UQAM";System.out.println( "Hello, "+uni+"!" );}}
      
    • Formatted code:
      package ca.uqam.info;
      
      public class App {
        public static void main(String[] args) {
            String uni = "UQAM";
            System.out.println("Hello, " + uni + "!");
        }
      }
      
  • 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

Formatting profiles

There are infinite ways to format code

  • Examples:
    • Some developers prefer tabs over whitespaces
    • Some developers want all {s on the next line
    • Some developers want methods to be indented only 2 spaces
    • ...
  • 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
    1. Go to Settings -> Plugins -> Marketplace
    2. Type "Checkstyle"
    3. Activate "Checkstyle plugin"
  • Activate the Google Checkstyle profile
    1. Download the Google Checkstyle configuration xml file
    2. Go to Settings -> Editor -> Code Style -> Java
    3. Click the cog-icon
    4. 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.

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)

  • 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
  • You'll see a search template, for all fields of all classes

    class $Class$ {
      $FieldType$ $Field$ = $Init$;
    }
    
    * But you can easily delimit search, e.g. to scope search to final fields:

class $Class$ {
  final $FieldType$ $Field$ = $Init$;
}

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)
  $Instance$.$MethodCall$($Parameter$)
  • 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.
  • Example:
    • click MethodCall and add a text filter: print. This reveals all methods print
    • To match also println, use a regex, i.e. print.*
  • Now you see all print statements, regardless of print, printf, or println.

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 require Fn key on laptops, to access F6)
    • 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 (and Fn 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:
    if(i< 3984574&&i%2==true||i ==42){
    ...
    }
  • You can replace parts of the expressions by named variables with Option (⌥) + Command (⌘) + v:

            boolean iSmallerMaxValue = i < 3984574;
            int correctAnswer = 42;
            boolean iIsEven = i % 2 == true;
            if(iSmallerMaxValue && iIsEven || i ==correctAnswer){
            ...
            }
    

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:

String myString = "foo";
foo. // IntelliJ will pop-up available String methods on "." typed.

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 to
      System.out.println();
      
    • psvm expands to
      public static void main(String[] args) {
      
      }
      
    • fori expands to:
        for(int i = 0; i < ...; i++) {
          ...
        }
      

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:

      Point p1 = new Point(2, 3);
      Point p2 = new Point(2, 3);
      System.out.println(p1 == p2); // returns false, these are different objects!
      System.out.println(p1.equals(p2)); // returns true, these objects are equal!
    

  • 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 generated equals 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.
  • To explicitly tell IntelliJ about the Maven build system:
    1. Right-click on the pom.xml file on the left
    2. Select Add as Maven project -> Reload

Configuring a Maven launch configuration

  • We can tell IntelliJ it should use Maven when the top "" is clicked.

    1. Click the dropdown (next to the "" icon)
    2. Select "Edit configurations..."
    3. Click the "+" icon, then select the "Maven" Option (⌥):
    4. 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 type clean package
  • 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 in foo * 42 to compute the immediate result.
  • 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:

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.