header-image

Code Quality Mindset

Home of the Quatas

CQM Blog

Prata 1: A Point About Breakpoints

Welcome to my first non-quata post. I decided to call this one a prata, which stands for productivity kata. I thought I’d do something different this time, and I picked this common mistake that I’ve seen new developers make when they pull me into their cubes and ask for help with debugging their code.

First of, why talk about productivity? In my opinion, a person who possesses a code quality mindset is also likely interested in enhancing their productivity and thus, their quality of life. They can do more work in less time. They can focus on their code more because they don’t have to struggle with other matters, such as the topic at hand.

Okay, let’s move along. What do you think about the screenshot below (don’t mind the code this time)? Do you fancy those red balls lined up on the left side of the code? If you think it looks perfectly normal, you’d better read on, my friend.

Unfortunately, this is a sign that the developer does not know how to use their IDE. Yep, this concept of using the right tools for the job is a recurring theme, I realize, and rightly so.

So, in this case, their intention is to be able to stop at each line and see it execute. I’ve never asked them how and why they started doing it that way, but I have a theory.

In IntelliJ IDEA at least, the Play-looking button (see the icon inside the red circle in the screenshot below), which is actually the Resume Program button, is more prominent or perhaps just better positioned than the Step tools (the ones inside the green oval). I don’t remember how it is anymore in other IDEs, but if a developer has only ever used IntelliJ, they have likely never noticed those Step tools.

The Resume Program button resumes the execution of your program until it encounters another breakpoint. I’ve literally seen developers add a breakpoint on the next line, click that button, then do it again and again, until their code is littered with breakpoints.

I then of course politely point them to those nifty Step tools above the window. The first icon is Step Over, which is exactly what they want to do in most cases. It executes each line, without stepping inside the methods, should you run into method calls.

There are also the Step Into and Step Out tools, which let you step into the method being called on that line and step out of the current method, respectively.

If you have never heard of these tools before, I highly encourage you to employ them in your next debugging session. They will likely look different in your IDE but these tools, however they look, and wherever they are positioned, are staples. If your IDE does not have them, switch to another one! S.e.r.i.o.u.s.l.y.

I think that you will find debugging more enjoyable (okay, less painful), and I’m sure that you’ll be more productive. It pays to explore your IDE.

Quata 8: Enum Fun

Okay, let’s get to it. What do you think about this code, well, besides the fact that the JDK already provides the java.time.DayOfWeek enum? Let’s pretend it doesn’t.

public enum DayType { WEEKDAY, WEEKEND, NULL }

public enum DayOfWeek { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }

public DayType determineDayType(DayOfWeek dayOfWeek) {
  if (dayOfWeek == null} {
    return DayType.NULL;
  }

  if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY)  {
    return DayType.WEEKEND;
  }

  return DayType.WEEKDAY;
}

There’s actually nothing wrong there. However, it could be a sign that the developer had not heard of how Java enums are now much more than constants. They were beefed up in 1.5 to behave like regular classes. They’re no longer your father’s enums. Let’s see how I would change the code to make full use of enums.

public enum DayType { WEEKDAY, WEEKEND, NULL }

public enum DayOfWeek {
  SUNDAY(WEEKEND),
  MONDAY(WEEKDAY) {
    @Override
    public isFunDay() {
      return false;
    }
  },
  TUESDAY(WEEKDAY),
  WEDNESDAY(WEEKDAY),
  THURSDAY(WEEKDAY),
  FRIDAY(WEEKDAY),
  SATURDAY(WEEKEND)

  private final DayType dayType;
 
  DayOfWeek(DayType dayType) {
    this.dayType = dayType;
  }

  public DayType getDayType() {
    return dayType;
  }

  public boolean isWeekday() {
    return getDayType() == DayType.WEEKDAY;
  }

  public boolean isWeekend() {
    return getDayType() == DayType.WEEKEND;
  }

  public boolean isFunDay() {
    return true;
  }
}

As you can see, enums can have their own methods and data members. What I like about the new code above is that the enum encapsulates the day type. A DayOfWeek enum value knows its day type best. It shouldn’t have to require its users to do that work, unlike in the original code where the user had to create the determineDayType() method. In the new code, we didn’t need to write that method anymore. Well, if you still must handle nulls, you could still write a little wrapper method, like so:

public DayType determineDayType(DayOfWeek dayOfWeek) {
  return dayOfWeek != null ? dayOfWeek.getDayType() : DayType.NULL;
}

The isWeekday() and isWeekend() methods are simply convenience methods I thought I’d add while I was at it.

I also added the isFunDay() method to demonstrate polymorphism. And yes, I picked Monday to be the not-fun day because that’s when many of us have to go back to work. Don’t get me wrong. I’m thankful for my job, but I’m sure you know where I’m coming from with this. I’ll let The Bangles do the rest of the talking.

The only thing about enums is that you cannot extend them. All of them inherently extend java.lang.Enum and therefore cannot extend any other class, including fellow enums. I would have extended java.time.DayOfWeek if it was possible.

Quata 7: String Addiction

Here’s another one on strings. The following method classifies the specified day of week into either a weekday or a weekend day (if that’s even a valid expression).

public String determineDayType(String dayOfWeek) {
  if (dayOfWeek == null) {
    return null;
  }

  if ("Saturday".equalsIgnoreCase(dayOfWeek) || "Sunday".equalsIgnoreCase(dayOfWeek)) {
    return "Weekend";
  }

  return "Weekday";
}

Would that work? Of course, it would.

But is that the best way to implement it? In most cases, no. (As with all things, it really depends on how and where this method is used.)

This is likely a case of not using the right tool for the job. Beginning developers tend to have String as their go-to type for anything. It’s like they’re addicted to it (hence, the title of this post). I think it’s usually just because they haven’t explored the rest of the language and are perhaps not even willing to.

If they did, then they would discover these wonderful gems called enums. Both the parameter and the return value have a small, finite set of values. Enums can therefore perfectly represent both of them. Look at this modified version.

NOTE: There is actually a java.time.DayOfWeek defined in the JDK. But let’s ignore it and use our own.

public enum DayType { WEEKDAY, WEEKEND, NULL }

public enum DayOfWeek { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }

public DayType determineDayType(DayOfWeek dayOfWeek) {
  if (dayOfWeek == null} {
    return DayType.NULL;
  }

  if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY)  {
    return DayType.WEEKEND;
  }

  return DayType.WEEKDAY;
}

Even though it’s longer, here are my reasons why this code is better than the previous one:

  1. Enums more naturally represent both the domain and the range of this particular function.
  2. Instead of returning null for a null value, it explicitly represents and returns a NULL enum value. The caller doesn’t have to worry about handling null return values.
  3. The original method actually had a bug. Since the string input could be anything, the method would return “Weekday” for any bogus non-null value, which is unlikely what you want. In this improved version, you would never even get a bogus non-null value because that’s just how enums work. They have a finite set of values. You cannot force them to have an unrecognized one.

There you go. If you’re addicted to strings yourself, you’re welcome.

I’m actually not satisfied with even this improved version. There’s a particular reason why I chose to ignore the JDK-supplied java.time.DayOfWeek enum. I’ll save my thoughts for the next quata though. Stay tuned.

Quata 6: String Concatenation

Let’s get right to it, shall we? Would any of the following ways of converting a list of names into a semi-colon-separated string pass your meticulous review? Yes, the trailing semi-colon is okay. Don’t worry about it.

String names = "";
for (String name : customerNames) {
  names += name + ";";
}

StringBuffer names = new StringBuffer();
for (String name : customerNames) {
  names.append(name + ";");
}

StringBuilder names = new StringBuilder();
for (String name : customerNames) {
  names.append(name + ";");
}

Which way is correct?

If you think it’s the first one, raise your hand.

I hope you didn’t. The String class is immutable. While the syntax does not reveal it, this actually creates a new String object each time. And since you’re assigning the new value to the same variable, you’re basically discarding the old value. That memory churn hurts my heart, doesn’t it yours?

What about the second one?

Keep that hand down. This is obviously a local variable (well, at least I hope it’s obvious), which should never need synchronization. StringBuffer operations are synchronized, which adds unnecessary overhead in this case. I still see this from time to time. The developer must not have heard about StringBuilder, which leads me to the next one.

If you think the third one is correct, raise your hand.

I’m sorry, but you’re still wrong.

While StringBuilder is the better class to use, the String concatenation inside the append() method is actually hurting your code. It’s creating a temporary String object, which is then appended to the StringBuilder object. And, you guessed it, it’s discarded right after. Do this instead:

StringBuilder names = new StringBuilder();
for (String name : customerNames) {
  names.append(name).append(';');
}

Note that the String concatenation operator does have its place. Consider the following:

String displayName = "First name: " + firstName + "; Last name: " + lastName;

That’s perfectly all right. There’s no loop here. We need one String object and that’s what we get. The bytecode generated will actually use StringBuilder internally. The above code will be compiled into the equivalent of:

String displayName = new StringBuilder().append("First name: ").append(firstName).append("; Last name: ").append(lastName).toString();

“There ” + “you ” + “have ” + “it.”

Quata 5: Conscious Coding

First off, I’d like to apologize for the long delay between the previous quata and this one. Life got in the way, in a good way, I would say. I found time to breathe tonight, and is there a better way to spend it than creating a new quata?

I won’t even attempt to give an introduction to this one lest I give away what it’s all about. I’ll let you look at the following code snippet first.

List<Pet> pets = readPetsFromDatabase(ownerId);
if (isAuthorized(userId)) {
  for (Pet pet: pets) {
    groom(pet);
  }
} else {
  throw new UnauthorizedException(userId, ownerId);
}

Okay, what did you find?

Correct (because my readers are all smart)! Line 1 didn’t have to be line 1. It should have been inside the if-block.

While it may seem obvious to some, I’ve seen this kind of mistake made a lot. As developers, we need to be conscious (hence, the title of this post) of where we place our statements. I could have used the title “Correct Placement”, but then, it would have been too obvious.

Correct placement is important, for readability and/or, in this case, for performance reasons. You do not want to do the DB read unless necessary. If the user is not authorized, you would have wasted that read, not to mention the memory you allocated for the collection.

I’ll probably use the title “Conscious Coding” a lot for future quick quatas like this, where a more specific title would ruin the surprise.

Quata 4: Mocking in Unit Tests

In Quata 3, I presented one half of my definition of “correct” when it comes to unit tests. In this quata, I will present the other half.

Consider the snippet below, which tests an ExchangeRateClient’s getRate() method.

@Before
public void setUp() {
  // code that launches a local HTTP server listening on port 8080
  // and returns canned responses.  How it's implemented is irrelevant.
}

@Test
public void extractsExchangeRateFromResponse() {
  SomeHttpClient httpClient = new SomeHttpClient("localhost", 8080);
  assertEquals(1.5, new ExchangeRateClient(httpClient).getRate("ABC", "DEF"), 0.001);
}

@Test(expected = RuntimeException.class)
public void handlesBadResponse() {
  SomeHttpClient httpClient = new SomeHttpClient("localhost", 8080);
  new ExchangeRateClient(httpClient) {
    @Override
    // getRate() calls extractExchangeRate()
    protected double extractExchangeRate(String httpResponse) {
      throw new RuntimeException();
    }
  }.getRate("ABC", "DEF");
}

The first test, the happy path, relies on actual HTTP server accepting requests and returning responses. Sure, it sets up its own server, but it has two problems:

  • Port 8080 could already be taken.
  • It’s slow. Unit tests are supposed to be lightning fast. You don’t want your builds to run long.

Let’s back up a little bit. SomeHttpClient is a dependency. You do not need to and should not test it itself here (which the above test does when it sends a request to the HTTP server). It’s the perfect candidate for mocking. You can program it however way you want to simulate the different scenarios that you want to subject ExchangeRateClient to.

In general, you should assume that external dependencies are doing their job. Feel free to mock them all away.


The second test goes the other direction. It mocks a method of the code under test. It passes because it is controlling its own outcome. It’s expected to throw a RuntimeException and it does, but only because you made it throw it. I’ve seen this kind of thing done a few times in the wild.

Again, the solution here is to back up towards SomeHttpClient. It is what you want to mock. Make it return a bad response and let ExchangeRateClient parse it.


Here’s the corrected version. It assumes the use of Mockito. You can however use your mocking framework of choice, including your own subclasses if you’re a DIY mocker.

The two tests are mocking SomeHttpClient, exactly as I prescribed above.

The code plugin I’m using is incorrectly displaying ampersands encoded. Sorry about that. I will fix it later when I get the chance.

@Before
public void setUp() {
  // no need to set up local HTTP server
}

@Test
public void extractsExchangeRateFromResponse() {
  SomeHttpClient httpClient = mock(SomeHttpClient.class);
  when(httpClient.get("from=ABC&amp;to=DEF")).thenReturn("{\"rate\" : 1.5}");
  assertEquals(1.5, new ExchangeRateClient(httpClient).getRate("ABC", "DEF"), 0.001);
}

@Test(expected = RuntimeException.class)
public void handlesBadResponse() {
  SomeHttpClient httpClient = mock(SomeHttpClient.class);
  when(httpClient.get("from=ABC&amp;to=DEF")).thenReturn("someBadResponse");
  new ExchangeRateClient(httpClient).getRate("ABC", "DEF");
}

Okay, here it is. The other half of my definition of “correct”:

A unit test is correct if it mocks everything that needs to be mocked, no more, no less.

The key is to find that sweet spot. And that sweet spot is usually the external dependencies.

In the real world, I actually often find myself overriding methods of the class I’m testing. It’s usually when the method I’m currently testing calls a bunch of other methods that have their own branches. If you test all combinations of paths through the calling method, you’d end up with tons of tests, unnecessarily at that. So instead of doing that, I would test the calling method with the other methods mocked out. Then, I test the other methods individually.

Under-mocking, as the first test originally did, leads to a likely-unnecessary dependence on external behavior, including system functions such as I/Os. When you find yourself writing a lot of set-up code, like the HTTP server setup code above (which I didn’t bother fleshing out), or relying on the presence of test resources, such as an actual test input file, you’re probably under-mocking. Ask yourself if you could mock the reading of that test input file instead of actually reading one.

Over-mocking, as the second test originally did, usually indicates confusion on the part of the developer. As you have seen above, it usually leads to the test testing itself.

Unit testing is certainly an acquired skill. I’ve seen many people struggle with it, the concept and all the tricky stuff. I plan to cover more unit-testing topics in the future, and hopefully discover the secret to making the brain grasp it more naturally.

Quata 3: Asserting in Unit Tests

Unit tests are, in short, safety nets for your code. There are only two possible reasons why a test breaks:

  • It needs to be updated to reflect the new code (syntax or behavior).
  • It is now broken because an unintentional functional change was introduced in the code.

On the other hand, when a functional change is intentional, but none of the unit tests break, either of the following is true:

  • The code is not covered.
  • The unit test(s) are not correctly written.

In this quata, I’ll present one half of my definition of “correct” when it comes to unit tests. I’ll cover the other half in Quata 4.

Consider the following assertions against a method that splits a string into a list of uppercase words.

List<String> words = myObj.splitAndCap("code quality mindset");
assertEquals(words.size(), 3);
assertNotNull(words.get(0));
assertTrue(words.get(1).equals("QUALITY"));

try {
  myObj.splitAndCap("code quality mindset");
} catch (MyException e) {
  fail("exception not expected!");
}

try {
  myObj.splitAndCap(null);
} catch (MyException e) {
  assertTrue(e.getMessage().contains("illegal argument"));
  assertEquals(IllegalArgumentException.class, e.getCause().getClass());
}

Here are my comments:

Line #Comment
2The parameters are switched. The assertEquals() method accepts the expected value and the actual value, in that order. Same thing with assertSame(). The order especially matters when the test fails. It will tell you which value it is expecting and which value it actually got. When they’re switched like this, the error message would confuse you.
3It’s usually not enough to assert that something is not null. If you know the expected value, then do assertEquals() or assertSame(). That way, when a bug is introduced that changes the value, your test would fail. assertNotNull() would keep passing. And that’s not right.
4If this assertion fails, it won’t tell you what the actual value was. When assertEquals() fails, it will. So, prefer it over assertTrue() wherever applicable.
5Wait, line 5 is a blank line. Well, it shouldn’t have been. We’re missing one more assertion. The test forgot about the third word! The code can mess up the third word and the test would still pass.

By the way, if your answer was, there’s a way to compare two lists in one shot, you get bonus points. I wanted to show different examples of incorrect asserts. Hence, the individual asserts on each element.
9This is redundant. There is no need to fail() here. In fact, we don’t need the entire try-catch business. It’s not technically incorrect, but it’s just not needed. Just let it throw the exception. JUnit will treat it as a failure, which is what you want.
14It’s the opposite situation here. We need a fail() on this line. We are expecting an exception and want to verify that the cause is an IllegalArgumentException. If someone introduces a bug that stops it from throwing that exception, it would not get to the catch block. Your test then automatically passes, when it should not. The fail() on this line would basically assert that “no, it shouldn’t get this far. It should go to the catch block instead.”
15assertTrue() comes in handy when you do not know the entire value, in which case, you tend to use methods such as String.contains() or String.startsWith(). However, when it fails, you wouldn’t know why. It would be prudent to pass the entire value as the first parameter to assertTrue(). It would show up in the error message when it fails, helping you figure out why the contains() or the startsWith() returned false.

Having said that, you should ask yourself whether you really need this assertion. I usually do not assert against exception’s messages, given how they’re pretty arbitrary. Over-asserting leads to fragile tests. If someone changes the exception message, I probably don’t want it to break my test. But of course, this is on a case-by-case basis. There may be cases where the exception message does matter and therefore should be asserted against.

Here’s the corrected code:

List<String> words = myObj.splitAndCap("code quality mindset");
assertEquals(3, words.size());
assertEquals("CODE", words.get(0));
assertEquals("QUALITY", words.get(1));
assertEquals("MINDSET", words.get(2));

// removed the unnecessary try-catch block around this call
myObj.splitAndCap("code quality mindset");

try {
  myObj.splitAndCap(null);
  fail("exception expected!");
} catch (MyException e) {
  // kept the line below assuming the message really matters;
  // note the first parameter;
  // ask yourself if you really need this assertion though.
  assertTrue(e.getMessage(), e.getMessage().contains("illegal argument"));
  assertEquals(IllegalArgumentException.class, e.getCause().getClass());
}

It’s time to present one half of my definition of “correct”:

A unit test is correct if it asserts everything that matters, no more, no less.

Under-asserting makes for incomplete tests. Think huge holes in the safety net, if you’d still call it that. I wouldn’t feel safe having that under me, would you? Changing something in the code that matters should fail the test. The above example failed to verify the value of the third entry in the array. That was a huge hole in the net.

Over-asserting, on the other hand, makes for fragile tests. In the above example, verifying the exception message could be considered over-asserting. If the exception message doesn’t really matter, your test should not verify it. Otherwise, any simple change in the message could unnecessarily break your test.

You will find the other half of my definition in the next quata.

Quata 2: Logging

When it comes to code quality, logging is unique in that the readability issue is not so much in the code as it is in the output, the log file. You want your logs to be truly helpful, especially when you’re troubleshooting an issue in the middle of the night. You’ll thank yourself for being nitpicky with your log messages. It’s not just about readability, however. There’s also performance.

The examples below are syntactically compatible with slf4j, log4j, and log4j2. However, many of the principles apply to any logging framework, Java or otherwise.

They are all incomplete snippets. They assume that we have a logger object called LOGGER. I realize there’s some debate on whether to make loggers static and on whether to name them as you would constants. It’s obvious which camp I’m on, but let’s not get into that. Let’s focus on the method calls, shall we?

LOGGER.info(" The VAlidaton  failed ");
LOGGER.info("Validation failed for" + field);
LOGGER.debug("Validation failed for " + field);
LOGGER.debug("Returning {}", someObj.toString());

// assume that each of the following is inside a catch (IOException e) block

LOGGER.error(e.getMessage());
LOGGER.error(e.getMessage(), e);
LOGGER.error("IOException occurred", e);

LOGGER.error("Failed to open file", e);
throw e;

LOGGER.error("Failed to open file", e);
throw new RuntimeException("Failed to open file", e);

Here are my comments:

Line #Comment
1Watch out for misspellings, random uppercase/lowercase letters, and extraneous words and whitespace. For example, do we really need the word The to be there?

Some people do not notice extraneous spaces. But I believe that with conscious effort and constant practice, anyone can train their eyes to be more cognizant of them.
2There’s no space between for and the value of the field.
3Debug is usually disabled in production. This message will therefore never be logged. However, enabled or not, that string concatenation will always happen, resulting to a new String object being created each time. Either you wrap it around an if-debug-enabled check or use the nifty parameter placeholders. In both cases, the string formation does not happen unless and until it is determined that the effective log level does allow for the message to be logged. The corrected code below uses the latter technique.
4Okay. This one is using a placeholder. Well and good. However, in this case, the value is a result of some object’s toString(), which could be a very expensive operation, and it will always be called. Despite the use of the placeholder, we’d still want to wrap this around an if-debug-enabled check.

log4j2 also supports lazy evaluation using lambdas. The corrected code shows an example of it.
8We usually want the exception’s stack trace to be logged. This only logs the message.
9This one correctly logs the stack trace by having the exception as the second parameter. However, the message is redundant because it will appear again in the stack trace. The message needs to be at the right abstraction. It needs to say what it was trying to do that failed, not something that we can’t already tell by looking at the stack trace.
10Another example of a redundant message.
11This one has a better message. It tells you what it was trying to do that failed. However, I would question the need to log it. Most likely, an outer layer will log it anyway. If that’s the case, logging it here is redundant and is therefore a waste of space.
12For the same reason as in the previous one, I would question the need to log it. As you probably know, the stack trace of the RuntimeException object will include the original exception as the cause.

Here is the corrected code:

LOGGER.info("Validation failed");
LOGGER.info("Validation failed for " + field);
LOGGER.debug("Validation failed for {}", field);
if (LOGGER.isDebugEnabled()) {
  LOGGER.debug("Returning {}", someObj.toString());
}

// log4j2's lazy evaluation using a lambda expression
LOGGER.debug("Returning {}", () -> someObj.toString());

LOGGER.error("Failed to open file", e);

// For the example that logs and re-throws the exception,
// let's assume we found that it is indeed being logged in an outer layer,
// in which case, we don't need the entire catch block at all.
// No need to catch the exception only to log and re-throw it.

// For the example that logs and wraps it in another exception,
// let's assume we found that the outer exception is being logged elsewhere,
// in which case, we don't need to log it here.
throw new RuntimeException("Failed to open file", e);

That’s about all I have for now. Thanks and see you in another quata.

Quata 1: Naming

Here it is, guys. The first ever quata.

I’ll start with proper naming of classes, methods, and variables. It’s one of the most important code quality skills you’ll want to master. You want your code to be expressive. You want it to be readable by other developers, and by the future you. Readability is maintainability, and proper naming plays a big role in that.

Java keywords and the JDK ‘terms’ are (mostly) in English. So, it certainly helps and is absolutely possible to have your code read like English. You just need to choose your names wisely.

The following is a very simplistic (i.e. not production-ready, as I anticipate most of my examples will be) implementation of a bank checking account. What would you change?

import java.util.ArrayList;
import java.util.List;
import java.math.BigDecimal;

public class ChkAcct {
  private BigDecimal availBal;
  
  public ChkAcct(BigDecimal amt) {
    availBal = amt;
  }
  
  public BigDecimal get_avail_bal() {
  	return availBal;
  }
  
  public void add(BigDecimal amt) {
	availBal.add(amt);    
  }
  
  public void subtract(BigDecimal bdAmount) {
    if (checkAmount(amt)) {
      availBal.subtract(amt);
    } else {
      throw new IllegalArgumentException();
    }
  }
  
  public boolean checkAmount(BigDecimal amt) {
    return availBal.compareTo(amt) >= 0;
  }
  
  public boolean isNotPositive() {
    return availBal.signum() <= 0;
  }
}

Here’s what I would correct:

Line #CommentSuggested Correction
MultipleIt’s littered with unnecessary abbreviations. Some developers try to save time typing by shortening their variable names. Today’s IDEs have superb auto-complete features that you almost never need to type the whole thing. These abbreviated names are harder to read. Spelling them out makes the code read more naturally.Spell them all out.
8, 16, 20Names should match the abstraction.

The constructor parameter amt is too generic. While you can kind of tell what it means, it needs to be more explicit than that.

The method names add() and subtract() are too technical and low-level.
Rename the constructor parameter to startingBalance.

Rename the add() and subtract() methods to the more context-appropriate deposit() and withdraw(), respectively.
12, 20Java uses camelCase for its naming convention. We should stick to it for consistency.

People coming from other languages tend to carry over their previous language’s naming convention.

The Hungarian notation, where you encode the type information as a prefix (the bd on line 20 stands for BigDecimal), is meant to assist developers writing in loosely-typed languages, which Java is not. It doesn’t serve any purpose in Java. It just causes disruption, therefore slowing you down.

I don’t know if there’s a term for the convention with underscores (please leave a comment if you know what it’s called), but while it’s not harder to read (probably easier for some), it’s just very un_java. Oops, I meant unJava. Consistency is king. Unless your entire organization has adopted that convention, stay away from it.

By the way, as you may know, underscores do have their place in Java constants. The convention is all-uppercase with underscores separating words, e.g. DEFAULT_STARTING_CHECK_NUMBER.
getAvailableBalance()
amount (drop the bd prefix)
21, 28checkAmount(), besides being vague, is not worded properly. It returns a boolean value. Its name should therefore be in a form that yields a yes-or-no answer.mayWithdraw()
32Negatively-named variables and method names usually make for very confusing expressions. Imagine calling !isNotPositive(). Ouch! That double negative hurts my brain.hasBalance(). I realize it changes the method altogether, but your callers will use what is made available to them.

Here’s the version with my corrections:

import java.util.ArrayList;
import java.util.List;
import java.math.BigDecimal;

public class CheckingAccount {
  private BigDecimal availableBalance;

  public CheckingAccount(BigDecimal startingBalance) {
    availableBalance = startingBalance;
  }
  
  public BigDecimal getAvailBalance() {
  	return availableBalance;
  }
  
  public void deposit(BigDecimal amount) {
	availableBalance.add(amount);    
  }
  
  public void withdraw(BigDecimal amount) {
    if (mayWithdraw(amount)) {
      availableBalance.subtract(amount);
    } else {
      throw new IllegalArgumentException();
    }
  }
  
  public boolean mayWithdraw(BigDecimal amount) {
    return availableBalance.compareTo(amount) >= 0;
  }
  
  public boolean hasBalance() {
    return availableBalance.signum() > 0;
  }
}

How did you do? Do you disagree with some of my recommendations?

There are other naming issues that I did not cover here. You can expect more quatas on proper naming in the future.