7 ms·
I agree with everything you said. As an old Perl guy, I found it hilarious that Perl has a reputation for illegibility, while I find it easier to read than mos
by ergothus 11y ago
I agree with everything you said. As an old Perl guy, I found it hilarious that Perl has a reputation for illegibility, while I find it easier to read than most Java, because in Perl I can express my intent, and in Java it's buried in the noise.
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?
- gknoy 11y ago> 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). 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 sorting
- deleted 11y ago[deleted]
- Retric 11y agoOne approach that can help is to name things based on what the functions actually do. validateSortDisplayedItems { validation Logic ... sortDisplayedItems(); //Actually sorts items. } This can be harder to maintain, but really long names end up a useful code smell.
- ergothus 11y agoI find it a bit...incorrect. 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.
- Retric 11y agoDepends on the Validation Logic. I like the style that basically works like this: 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.
- kazinator 11y agoThe problem is that the validateSortDisplayItems is some sort of public function. The caller doesn't care about the validation, only that the items are sorted. All kinds of functions everywhere validate their arguments; that doesn't deserve to be in their name.
- Retric 11y agoThe question is why you need a helper function? IMO, public functions are a special case and your generally better off using different naming schemes for internal and external functions. 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.
- yekim 11y agoWrt Perl being readable / legible; I think most find Perl hard to read because there are 3-4 different ways to do the same thing. And many of the more "advanced" ways of doing things rely on short, terse single characters that act essentially like magic and behave differently in different situations. At least this is what I remember as I climbed the Perl ladder. Contrast 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?
- ergothus 11y agoI won't disagree with most of what you said (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). 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.
- bcheung 11y agoMake them separate functions each with their own local state or use namespacing. The problem sounds like too much state in a single algorithm. Break it down into subalgorithms each with their own state.
- kazinator 11y agoI always call the low level functions with some clear name related to the high level one by convention: - sort_displayed_items_guts - sort_displayed_items_impl - ll_sort_displayed_items (ll == low level) - do_sort_displayed_items
- 0xfadedace 11y agoA convention I've seen in Java libraries and that I'm fond of is: public sort() { preconds() try { doSort() } catch (ex) { handle(ex) } postconds() } private doSort() throws Exception { // core impl }