You're absolutely right: naming things is hard. When I was writing Scheme long ago [in university], our convention was to name helper functions with `-helper` in the name:
sort-foos
sort-foos-helper
sort-foos-merge-helper
Now that I write mainly in Python, the convention seems to be to use underscores to indicate things that you should ignore. Usually that's done at a method level, where your consumers are calling the object's public API (sort), so the object's namespace helps reduce such name collisions. The addition of triple-quote docstrings for method comments helps a lot at clarifying purpose as well: class FooSorter(object):
def sort(self, foos):
"""Sort an iterable of Foo objects"""
# ... sanity-check inputs ...
self._sort(foos) # do the sort operation
def _sort(self, foos):
"""
Sort things we already know are Foo objects.
This is a helper for sort(), which already
sanity-checked our inputs.
"""
# do the actual sortingContrast this with say, Python, where readability is highly valued and they have a one-way-to-do-certain things mentality, like sorts for example, means code is generally easier to grok, debug, and extend.
Thus, readability becomes especially important when you have to deal with someone else's big ball o' mud and you need to fix a high stress, high visibility, lines down issue at one in the morning because the original programmer is long gone, didn't leave any comments in their code, and decided to play "look at me, I'm so clever with how I use this operator", but forgot to account for a certain scenario that manufacturing decided to roll out the other day without telling anyone. Can I get an amen?
That said, for every time I've hated that someone got terse and clever, I've loved that I wrote something that _read_ well, particularly when returning to previous code I don't remember. Coding is like a language, you can use that expressiveness to be terse, to be rambling, or to be clear. Learning to do so is a skill that has to be learned (cost), but can be more expressive (benefit). Languages (or patterns) that restrict that reduce the cost, and reduce the benefit.
Consider, for example, that Python takes great pride in it's explicit readability, and yet it chose "lambda" as a keyword (making no one happy outside of higher math), that "def" was chosen instead of "define", and that one of the most powerful parts of the language (comprehensions) are highly prone to using secret knowledge. [] works differently than (), for example.
So, what you've described are all valid reasons that Perl has a bad rep...but I don't blame Perl for them anymore than I blame JS for the hideousness that is the DOM, or Python for the bad scripts that have been written in it. Regardless of the language, cleverness and/or terseness without regard to future people (including your future self) is just not a good practice.
though I will say that having one way to do it does NOT mean that
said one way is particularly readable/maintainble - looking at you,
Java
..
So, what you've described are all valid reasons that
Perl has a bad rep...but I don't blame Perl for them
anymore than I blame JS for the hideousness that is the
DOM, or Python for the bad scripts that have been written
in it.
Will you be willing to extend the same charity to the bad Java code you have seen? Or do you have specific complaints for your harsh comments singling out Java?Yes and no.
For the bad Java code I've seen, Java does not bear the responsibility. (And I've seen my fair share of bad Java code because I spent 5 years as a Java dev in a place that had a collection of bad coders and worse code). I've also (since) seen great coders with quality code (in Java), but I definitely retain some bias from my earlier experience.
BUT, when your language design promotes certain things, then yes, that language takes the hit. For example, some of the Perl options are just plain terrible (e.g. changing 0 indexing) and Perl takes the blame. Perl also moved away from those, recognizing the errors. Books like Higher Order Perl (and associated efforts) came out to promote best practices.
Java expressly sought verbosity, and thus takes the blame for verbosity at the expense of clarity.
Other practices are encouraged by the community. That's a gray area - it's not the language at fault, but...it's common. These can range from small and almost petty (Perl promoted underscores in variable names for REASONS, while Java promotes camelCaseBecauseIGuessSomePeopleLikeToSquintToParseVariables.) Not really the LANGUAGE fault, but definitely an expectation. (and one I've grown accustomed to in the name of working with others). Other problems are larger (Kingdom of Nouns) - still not the Language fault, but you can expect it if you're dealing with the code.
Many a time I've tried to trace through some Java code...weaving through "Impl" classes and factories, trying to find where some piece of logic is implemented...only to find myself in an empty class definition. That's a fault of both encouraged practices and poor practices.
When people complain of poor Perl code, it's usually code that was either written by someone that didn't do that as their main job, code from newbies, or code that has changed hands repeatedly with no one trying to make it maintainable. Most any code will suffer in those conditions. Java, thus far, has had the worst overall quality of code in "professional" collections that I've experienced. But then again, I've really only seen code in any quantity in 3 or 4 languages, so....anecdotal experience is anecdotal and subjective.
(I have to give props to Python here...while I have a few nits about a few things, and I'm not well-versed enough to discuss code quality of anything I skim, I've seen a lot of code that was probably "bad" but nonetheless avoided some mistakes that are commonly made in other languages, particularly with new coders.)
validateSortDisplayedItems
{
validation Logic
...
sortDisplayedItems(); //Actually sorts items.
}
This can be harder to maintain, but really long names end up a useful code smell.I mean, your above code LIES. If I call validateSortDisplayedItems, I don't validate, I validate AND sort. Plus, what do you do if you have "validateItems" and "sortItems", and then one function that calls them each in turn? call it "validateAndSortItems"? Yuck.
validateSortDisplayedItems
{
if(!DisplayedItemsValid())
{
CorrectDisplayedItems();
if(!DisplayedItemsValid())
{
DisplayValidationError();
return;
}
}
sortDisplayedItems(); //Actually sorts items.
}
AKA validate means try and make valid, not verify that data isValid. So, you can't just do if(isValid) sort; the bonus is unrecoverable errors end up at leaf nodes vs. the happy path.At the high level, your function might be sortClicked, which can then respond to that by calling a wide range of functions. (userCanSort,SortData,UpdateDisplay)
PS: I find the validate > correct loop is generally the important and error prone part of code, so I give it priority. The happy path where everything works is more or less an addendum.
All kinds of functions everywhere validate their arguments; that doesn't deserve to be in their name.
Granted, if it's pure pass-through then reusing the name is a non issue. Foo(x){foo(x);} is not confusing. Foo(x){junk; foo(x);} is.
One option is Foo(x){validateForFoo; ValidedFoo(x);}
Alternatively for private functions JunkFoo(x){junk(x); foo(x);} And replacing JunkFoo with a meaningful name if possible.
Again though, public function Foo(x) should really call internal function Bar(x) otherwise it's really easy to couple things on both sides of an interface.
If someone wants to know how to name the low-level helper which does the real work and the interface over it, we just have to take it for granted that the separation is necessary and that it makes sense for either function to have the name "displaySortedItems" or whatever.
SortGuiElement(){junk; SortUserNames(); junk;}
SortUserNames(){junk;}
vs. SortNames(){junk; sortNames() junk;}
sortNames(){junk;}
It might seem obvious and the code might be identical, but I see the second case very frequently in other peoples code.You want:
SortNames(){junk; SortNamesImpl(); junk;}
sortNames(){junk;}
or whatever: SortNamesGuts(), InternalSortNames(), DoSortNames(), LowLevelSortNames(). Anything but just flipping the case of a letter or two in the "SortNames" identifier.Amway IMO, SortNames() and Internal/Do/LowLevel/SortNames() are almost as bad because just looking at the names it's not obvious what's going on. Yes, visually they look different which helps and consistency can make things even more readable. But, even just SortNamesHappyPath() gives some idea of what's going on.
public sort() {
preconds()
try {
doSort()
} catch (ex) {
handle(ex)
}
postconds()
}
private doSort() throws Exception {
// core impl
}- sort_displayed_items_guts
- sort_displayed_items_impl
- ll_sort_displayed_items (ll == low level)
- do_sort_displayed_items
One cognitive problem I've not yet found a good solution to:
So I have a high level routine to, say, sort the items in a display. Said sorting has some cascading effects, so I'll have a high level function that calls some mid level functions that call some lower level functions.
But at some point I get a name clash between a high level function and a low level one. Like...I'm calling the high level sortDisplayedItems, (or sort_displayed_items) and there's a low level function that ACTUALLY sorts them (as opposed to handling the other bits). Sure, I can give it a distinct name (e.g. sort_items), but when skimming the code, it's unclear which is which. If I have to mentally parse the code to understand what is happening, I've failed at readability/maintainability.
How have others handled this?