In a chapter, talking about different ways you could write a class that generates verses for the song 99 bottles of beer, we end up at:
https://sandimetz.com/99bottles-sample-ruby#_shameless_green
The Shameless Green solution is disturbing because, although the code is easy to understand, it makes no provision for change. In this particular case, the song is so unlikely to change that betting that the code is "good enough" should pay off. However, if you pretend that this problem is a proxy for a real, production application, the proper course of action is not so clear.
When you DRY out duplication or create a method to name a bit of code, you add levels of indirection that make it more abstract. In theory these abstractions make code easier to understand and change, but in practice they often achieve the opposite. One of the biggest challenges of design is knowing when to stop, and deciding well requires making judgments about code.The requirements changed and it now has to handle a new case? Now you can refactor it. What refactoring to choose depends on the new case you have to support - it's going to look a lot different if you need to accept a new "language" parameter than if you need to special-case another number.
That being said, the first time I had to edit this (even just do change the base string) I’d make the change.
private String generateGuessSentence(char candidate, int count) {
if (count === 0) return "There are no " + candidate + "s";
if (count === 1) return "There is 1 " + candidate;
return "There are " + count + " " + candidate + "s";
}It just needs one tired / inexperienced / somethingelse coder to "quickly add" an extra call to the topmost if and then you'll have interesting issues.
I'll take the "ugly" braces every day compared to having risky structures like that in the code at all.
But easier maintainability I also favor braces everywhere.
return "There are no " + candidate + "s";
I always hated overloading + for both addition and concatenation. Hence D uses ~ for concatenation. "10" + 1
does that mean 11 or "101" ? // this may be a mistake! English language is unorthogonal in the extreme!
const char * Add_es( int count ) { return 1 == count ? "" : "es"; }
const char * Add_s( int count ) { return 1 == count ? "" : "s" ; }
(some English words pluralize with "es" suffix, most with "s"). It has proven surprisingly useful: Msg( "%d file%s updated when you switched back", updates, Add_s( updates ) );
Only difference vs example is mine prints "0" vs "no".For example, many languages have grammatical gender and so may also change how the word looks like. Bonus points, when the word body changes. In this example it wouldn't change radically, but the word for a file in German is "die Datei" and the plural is "die Dateien" (although apparently "das File" can be used.)
Okay, this isn't that big of a deal, one might say. You just pack the word's singular and plural with the possible gender, and you're set. But no, because this isn't even the worst part! The simplest complicated case might be that you have a singular, dual, and plural. In some sense this makes sense, since "two" is pretty special in terms of amount of stuff.
But then you get stufi like the following, as documented in [0]:
> Three forms, special cases for numbers ending in 1 and 2, 3, 4, except those ending in 1[1-4] > > [...] > > Languages with this property include: > > Slavic family > > Russian, Ukrainian, Belarusian, Serbian, Croatian
Or the six form pluralisation found in Arabic.
So yeah, this sort of "complicated" pluralisation isn't just an issue if you're trying to support some comparatively very niche languages, but this is a thing one needs to think about when supporting many major world languages.
And of course, how you inflect the noun you're pluralising depends on the context of the sentence and the relevant grammar. For example, the form might change if you're at a place that expects a noun in its nominative versus accusative form. And of course, there's the order of words, and so muth more, that could be mentioned.
So yeah, natural language is difficult. Not quite Turing complete, but good localisation takes a lot of effort.
[0]: https://www.gnu.org/software/gettext/manual/html_node/Plural...
defmodule Guess do
def guess_message(candidate, _count=0) do
"There are no #{candidate}s"
end
def guess_message(candidate, _count=1) do
"There is 1 #{candidate}"
end
def guess_message(candidate, count) do
"There are #{count} #{candidate}s"
end
end
If anything, I would go in the opposite direction. There's no need to use parameterized variables at all. Just write out the message 3 times:
Maybe this isn't "clean" (I'd claim it is) but it's certainly the easiest to understand.