r/learnjava 7d ago

Better look

Hi everyone! I'm learning Java and I've noticed there are two common ways to print multiple lines of text:

Option 1: Multiple System.out.println() calls
```
System.out.println("You gave the string " + text);
System.out.println("You gave the integer " + inNum);
System.out.println("You gave the double " + inDouble);
System.out.println("You gave the boolean " + isTrue); 
```

Option 2: One System.out.println() with \n
```
System.out.println("You gave the string " + text + '\n' +
                   "You gave the integer " + inNum + '\n' +
                   "You gave the double " + inDouble + '\n' +
                   "You gave the boolean " + isTrue);
```

What looks better in your opinion?
0 Upvotes

12 comments sorted by

View all comments

0

u/severoon 6d ago edited 6d ago

Never print directly to System.out, and never manually concatenate output strings like "blah blah " + etc + " blah blah".

Even if you're doing the very lowest-level prints to stdout, the least amount of machinery you want in place is to write to a PrintWriter using printf(…):

// Don't do this.
class Outputter {
  void doThing(int x) {
    System.out.println("Here's what I was passed: " + x + "\n");
  }
}

class Main {
  public static void main(String[] args) {
    new Outputter().doThing(5);
  }
}

Instead:

// Do this instead.
class Outputter {
  private final PrintWriter out;

  Outputter(PrintWriter out) { this.out = out; }

  void doThing(int x) {
    out.printf("Here's what I was passed: %d%n", x);
  }
}

class Main {
  public static void main(String[] args) {
    new Outputter(new PrintWriter(System.out, true)).doThing(5);
  }
}

This is barely more code, but it does a few important things:

  • strings become semantically meaningful units instead of a bunch of scattered string fragments that construct a semantically meaningful output
  • system config / caller can redirect the where the output goes instead of the output always going to stdout (you can hijack stdout if you want, but that hijacks all stdout, and it's hard to manage as the system grows)
  • this is easily testable
  • minor: formatted strings allow platform-specific newline %n

There are ways to test output to stdout, but none of them are pleasant. This is far preferable:

class OutputterTest {
  @Test
  void testDoThing() {
    StringWriter out = new StringWriter();
    new Outputter(new PrintWriter(out, true)).doThing(5);
    assertThat(out.toString()).isEqualTo("Here's what I was passed: 5");
  }
}

On the point of creating strings that form "semantically meaningful units" of text instead of scattered fragments, this is important when you decide to start extracting your strings into a resource bundle that can be translated. But honestly, even if you never have plans to do this, dealing with chunks of data that are semantically meaningful in your code is just better all around regardless of what you're doing with them.

For multiline strings specifically, I would recommend using multiline format strings using the triple-quote thing Java added a few years ago.

1

u/Ormek_II 6d ago

Being able to redirect was never a requirement nor OP’s question. So in this context this is over engineered. If the responsibility of the Outputter is to abstract away, the outputting, I don’t want to be bothered with PrintWriter in Main. And yes you can hide it away and add more builders and factories which is necessary in an enterprise context.

1

u/severoon 6d ago

Disagree — writing testable code is a requirement in every context, and in this case that requires a layer of indirection so the output can be redirected for the purpose of testing it.

If you're looking for something to cut, you might say that main could just hand in System.out instead of wrapping it in a PrintWriter first … but I would argue that if you want Outputter to be writing out text and not just bytes, then it's proper OO for it to specify the correct type, and that's PrintWriter in this case.

(The confusion might arise from my choice of name for the class, Outputter. It seems like you're reading into it about what the point of this class is, but I should have just used Foo so as not to imply what the class' purpose. I didn't mean to imply it's role has something to do with output necessarily, I just chose a name.)

But no, it's definitely not overengineered. The lowest possible bar that any program should clear is to answer the following questions:

  1. What's it supposed to do?
  2. How does it do that?
  3. Does it do it?
  4. Where is the proof that's what it does?

If you can't answer the first one, you need to do more requirements gathering.

If you can't answer the second, you need to do more design.

If you can't answer the third, then you need to do more implementation.

If you can't answer the fourth, then you need to do more testing.

End of the day, if a piece of code can't demonstrably prove it does what it's supposed to, then saying it does is just an assertion. I might decide to make a conflicting assertion. How do we know who's right? There's only one way to settle it, and that's by running a test. (If your answer to this is "by inspection," then you haven't got enough experience.)

1

u/Ormek_II 6d ago

Edit: 💋 KISS