loading...

Live Coding Learnings - June 21, 2019

jitterted profile image Ted M. Young ・4 min read

Quizdown: Episode 20

The 20th episode of working on Quizdown, a Java+Spring-based app that uses a Markdown-like syntax to create coding quizzes.

Stories Implemented

After a non-trivial refactoring (see No Naked Data Structures below),
I now have the ability to move back and forth between questions. The HTML generation now generates checked if that choice was previously selected. Next step is to load the user's responses from the database and send that to the UI controller.

HTML: Use disabled Attribute for Button or Link

When I was looking for the proper CSS class to use to display a button as disabled, I found that instead of using, say, an is-disabled CSS class, HTML supports disabled as an attribute on both button and link (<a>). For example, to show the link as disabled instead of:

<a class="is-disabled" href="/question/0">Previous Question</a>

you would use the disabled attribute like this:

<a href="/question/0" disabled>Previous Question</a>

No Naked Data Structures

One of my Java design heuristics is No Naked Data Structures, which means wrapping raw data structures (List, Map, etc.) in a type so you can pass it around and interact with it in a way that makes sense from a domain point of view.

This is similar to the No Stringly-Typed Code heuristic: don't use String everywhere, instead use class and interface types. For details see here and here.

When adding the feature to allow users to go back and forth through the questions, I needed to have the choices that the user made be available. Previously I was storing those choices as a Set<String>, as it was straightforward and it wasn't being used in many places. However, once I needed it in more places (pushing it back up to the front-end from the database), handing around a "naked" Set made the code a bit harder to read and work with. For example, with the Set, figuring out if it's correct depended on the type of question (FIB is a fill-in-the-blank and MC is multiple-choice):

  public boolean isCorrectFor(@NonNull Set<String> response) {
    return switch (questionType) {
      case FIB -> correctChoices.containsAll(response);
      case MC -> correctChoices.equals(response);
    };
  }

This is pretty typical code, but unless you're familiar with the specifics of Set, you may have to do a bit of JavaDoc reading to ensure you understand it correctly. By replacing it with a Response type that encapsulates the Set, the code above becomes:

  public boolean isCorrectFor(Response response) {
    return switch (questionType) {
      case FIB -> response.matchesAny(correctResponse);
      case MC -> response.allMatch(correctResponse);
    };
  }

Now, this may not be such an amazing improvement (I'm still toying with the naming), but it leads to the next step (which I haven't done yet) of pushing the behavior that's specific to the question type into a subclass of Response, e.g., a FibResponse and McResponse, which would look like:

  public boolean isCorrectFor(Response response) {
    return response.correctlyMatches(correctResponse);
  }

Completely delegating how responses are compared to the class that knows best how to do that comparison.

In my training and coaching of Java developers, the use of Stringly types and Naked Data Structures is the cause for much pain when things change, so always try and encapsulate Strings into Value Objects and wrap up your data structures into its own class.

Exposing asSet() on Response: breaks Encapsulation?

By encapsulating the Set<String> inside of Response means that when I need to display the response choices or persist them to a database (i.e., hand the response across the domain boundary) I need to get those choices as a set, so the Response class has a method

public Set<String> asSet() {
  return Set.copyOf(response); // return a copy of our internal field
}

That returns the choices as a Set<String>. I use this in the GradedAnswerView class to convert it to a comma-separated String:

  private static String responseAsString(Answer answer) {
    return answer.response().asSet()
                 .stream()
                 .sorted()
                 .collect(Collectors.joining(", "));
  }

You might think: oh no, this is breaking encapsulation by exposing the set, but I'm not really doing that, because I'm returning a copy of my internal storage of the response choices. I never expose the field itself, so the only way to modify the internal Response data is only through its methods. Modifying the copy would have no effect.

Empty varargs

I don't think I had realized this before, but calling a method defined with varargs (e.g., createFrom(String... strings)) means that you can call that method with no arguments: createFrom(), and inside this method, the strings argument would be an empty array.

This means I didn't have to special-case turning varargs into an empty Set, because calling Set.of() with no arguments will turn into an empty Set (technically an ImmutableCollections.emptySet() as of Java 9), which is what I'd want.

Collaboration Tests

Most of my tests are unit tests, in that they are testing code in a class by working with that class as closely as possible. For example, the Choice class can be directly tested without any other objects. However, the QuestionTransformer can't be tested on its own as its job is mostly to collaborate with more specific transformers to do its job, i.e., its job is to integrate or collaborate with the other objects. This means that I don't need to comprehensively test all of the behavior of all of the classes that are involved, just a test or two that would ensure that QuestionTransformer is correctly collaborating with (in this case, propagating the response to) other objects.

You can see a clip from my stream where I do this: https://www.twitch.tv/videos/443315061.


Be sure and tune in to my next live coding stream: https://twitch.tv/jitterted.

Want to support me? Become a Patron at https://patreon.com/jitterted

Discussion

pic
Editor guide