Skip to content

Exceptions

William Wood edited this page Feb 22, 2024 · 4 revisions

Exceptions

In object-oriented programming we deal with things going wrong by things called "Exceptions". These are essentially special types of objects that your code can "throw" (or "raise" in the case of Python). If your code "throws" an exception then something should "catch" it and deal with it accordingly.

Instrument objects from JISA can throw exceptions when something goes wrong. For example, it may throw an exception when trying to connect if a connection cannot be established or when trying to take a reading if the instrument takes too long to reply (ie a time-out).

Understanding and being able to deal with exceptions is important for creating robust code.

Contents

Try-Catch Blocks

The most important part of this is understanding how to catch an exception and deal with it. As an example, let's attempt to connect to a Keithley 2600B over TCP-IP and take a voltage measurement:


Java

SMU smu = new K2600B(new TCPIPAddress("192.168.0.5"));
smu.turnOn();

double voltage = smu.getVoltage();

smu.turnOff();
smu.close();

Kotlin

val smu = K2600B(TCPIPAddress("192.168.0.5"))
smu.turnOn()

val voltage = smu.getVoltage()

smu.turnOff()
smu.close()

Python

smu = K2600B(TCPIPAddress("192.168.0.5"))
smu.turnOn()

val voltage = smu.getVoltage()

smu.turnOff()
smu.close()

Calling the K2600B() constructor like this can throw an exception if the connection fails or is lost during the measurement. To be able to catch such an exception, we must put this code inside a try ... catch (or try ... except in Python) block:


Java

try {

    SMU smu = new K2600B(new TCPIPAddress("192.168.0.5"));
    smu.turnOn();

    double voltage = smu.getVoltage();

    smu.turnOff();
    smu.close();

} catch (Exception e) {

    GUI.errorAlert(e.getMessage());

}

Kotlin

try {

    val smu = K2600B(TCPIPAddress("192.168.0.5"))
    smu.turnOn()

    val voltage = smu.getVoltage()

    smu.turnOff()
    smu.close()

} catch (e: Exception) {

    GUI.errorAlert(e.message)

}

Python

try:

    smu = K2600B(TCPIPAddress("192.168.0.5"))
    smu.turnOn()

    val voltage = smu.getVoltage()

    smu.turnOff()
    smu.close()

except Exception as e:

    GUI.errorAlert(e.getMessage())

What this structure does is it tries to run what is inside the try { } block and if an exception is thrown it will look for a corresponding catch (...) (or except) block that matches the type of exception thrown and run that instead.

In our case, we have one catch block designed to catch exceptions of type Exception. As it happens, all exception types extend from Exception so this block will catch all exceptions. However, we could be more specific. Instruments in JISA can throws an IOException if something goes wrong with communications or a DeviceException if there some sort of compatibility issue. Therefore, we can add multiple catch blocks to catch each different type of exception to produce a more specific error message, as well as leaving an Exception catch block to catch anything else that isn't one of our expected types:


Java

try {
    ...
} catch (IOException e) {
    GUI.errorAlert("I/O Error", e.getMessage());
} catch (DeviceException e) {
    GUI.errorAlert("Device Error", e.getMessage());
} catch (Exception e) {
    GUI.errorAlert("Error", e.getMessage());
}

Kotlin

try {
    ...
} catch (e: IOException) {
    GUI.errorAlert("I/O Error", e.message)
} catch (e: DeviceException) {
    GUI.errorAlert("Device Error", e.message)
} catch (e: Exception) {
    GUI.errorAlert("Error", e.message)
}

Python

try:
    ...
except IOException as e:
    GUI.errorAlert("I/O Error", e.getMessage())
except DeviceException as e:
    GUI.errorAlert("Device Error", e.getMessage())
except Exception as e:
    GUI.errorAlert("Error", e.getMessage())

Scope

For Java and Kotlin it is important to remember the scope of your variable. Specifically, if we were to put a try ... catch block around connecting to our SMU like so:


try {
  SMU smu = new K2600B(new TCPIPAddress("192.168.0.5"));
} catch (Exception e) {
  ...
}

// This will fail! smu is not defined outside the try block
smu.turnOn();

try {
  val smu = K2600B(TCPIPAddress("192.168.0.5"));
} catch (e: Exception) {
  ...
}

// This will fail! smu is not defined outside the try block
smu.turnOn()

then we cannot access the variable smu from outside that try block since we declared it inside it. Instead, we must declare smu first, outside the block like so:


Java

SMU smu;

try {
  smu = new K2600B(new TCPIPAddress("192.168.0.5"));
} catch (Exception e) {
  ...
}

// This will now be fine
smu.turnOn();

Kotlin

val smu: SMU

try {
  smu = K2600B(TCPIPAddress("192.168.0.5"))
} catch (e: Exception) {
  ...
}

// This will now be fine
smu.turnOn()

Because Python does not let you declare variables properly (instead only letting you assign them), it has no proper concept of scope. Therefore you would be fine writing:


Python

try:
  smu = K2600B(TCPIPAddress("192.168.0.5"))
except Exception as e:
  ...

smu.turnOn()

The code in your catch/except block in this case should do something that stops the programming continuing on to the smu.turnOn() line (such as return) since the connection failed and smu will not be initialised.

void myFunction() {

  SMU smu;

  try {
    smu = new K2600B(new TCPIPAddress("192.168.0.5"));
  } catch (Exception e) {
    GUI.errorAlert(e.getMessage());
    return;
  }

  // This will now be fine
  smu.turnOn();

}

Finally Block

In extension to the try { } catch { } structure, you can add a finally { } block. Code in this block will run after that in the try { } and any catch { } blocks regardless of whether an exception was thrown or not.

This is often useful to make sure an instrument is properly turned off/disconnected after a measurement, regardless of whether it went wrong.

For example:


Java

try {

    smu.turnOn();

    for (double v : Util.makeLinearArray(0, 60, 61)) {
        smu.setVoltage(v);
    }

} catch (Exception e) {

    GUI.errorAlert(e.getMessage());

} finally {

    smu.turnOff();

}

Kotlin

try {

    smu.turnOn()

    for (v in Util.makeLinearArray(0, 60, 61)) {
        smu.setVoltage(v)
    }

} catch (e: Exception) {

    GUI.errorAlert(e.message)

} finally {

    smu.turnOff()

}

Python

try:

    smu.turnOn()

    for v in Util.makeLinearArray(0, 60, 61):
        smu.setVoltage(v)

except Exception as e:

    GUI.errorAlert(e.getMessage())

finally:

    smu.turnOff()

In the above example, we make sure that smu.turnOff() is called at the end regardless of whether the code in the try block completed successfully or not.

Throwing Exceptions

If you wish to throw your own exceptions, you need only use the throw keyword (or raise in Python). For instance, if we make a function that returns the result of dividing two numbers, we will want it to throw an exception if the denominator is zero (ie cannot divide by zero).

You can do this by creating an Exception object, then give it to throw/raise. In Java, if your method can throw an exception you need to state which exceptions it can throw in its declaration by adding throws ... to the end.


Java

double divide(double numerator, double denominator) throws Exception {

  if (denominator == 0) {
    throw new Exception("You cannot divide by zero!");
  }

  return numerator / denominator;

}

Kotlin

fun divide(numerator: Double, denominator: Double) : Double {

  if (denominator == 0) {
    throw Exception("You cannot divide by zero!")
  }

  return numerator / denominator;

}

Python

def divide(numerator, denominator):

  if denominator == 0:
    raise Exception("You cannot divide by zero!")

  return numerator / denominator;

Clone this wiki locally