• PEP-8, Line Length, And All That

    From Thomas Passin@21:1/5 to All on Sat Jan 21 01:32:12 2023
    In another thread ("Improvement to imports, what is a better way ?")
    there was a lot of talk about line length, PEP-8, etc. I realized that
    one subject did not really come up, yet it can greatly affect the things
    we were talking about.

    I'm referring to the design of the functions, methods, and classes.
    When they are well designed, or more likely, refactored over and over
    again, they can lead to code that reads almost like pseudo-code. Here's
    an example from one of my little projects. You don't need to know
    anything about the details of the functions or exactly what a "root" is
    to see what I mean.

    fileinfo = language, path, ext = getExeKind(root)
    processor = getProcessor(*fileinfo)
    runfile(path, processor, ext)

    [Please don't try to guess exactly what this code needs to do or explain
    how it could be done differently. That's not the point.]

    In words, given a source of information (root), we can get some
    information about a file. Given that information, we can find a
    suitable processor for it. And given that processor, we can "run" it.

    When I first put this together, the functionality was not cleanly
    separated, the various functions did more than one thing, and I had some
    long lines. Each line did not necessarily convey cleanly what it was doing.

    It took many iterations while I learned how to make the details work
    before I was able to see how to structure this part of the functionality
    into these three nice, clear lines.

    In fact, the restructuring isn't quite finished, because the near-final
    version of runfile() does not actually use "ext" (the extension of a
    file) any more. Its presence is leftover from when runfile() tried to
    do too much.

    Why did I assign the (language, path, ext) tuple to fileinfo? Because
    it was easier and shorter when used as the argument for getProcessor(),
    and I thought it conveyed the intent more clearly than the tuple.

    Some people might think to write

    processor = getProcessor(*getExeKind(root))

    Oops, that doesn't expose path and ext. Well, not if we are going to
    avoid using a walrus operator, anyway, and if we used it, well,
    readability would go out the window.

    In a different context, a "fluent" style can be very readable and
    pleasant to work with. Here are some lines from a Windows batch file
    that invokes a small Python library written in a fluent style. The
    first line defines a processing pipeline, and the second passes a data
    file to it ("xy1" is the feeble name for a batch file that calls the
    fluent processing library):

    set process=low(30).diff().norm_last_n(%N%).write()
    type "c:\data\%1" |xy1 %process% >> %temp%

    In words, we smooth the data with a LOWESS smooth using a window width
    of 30, differentiate it, normalize it in a specific way (norm_last_n),
    and write it out (to stdout).

    Not shown here, we eventually pipe it to a plotting program.

    I intended from the start that this library would work in a "fluent"
    manner. It took a lot of iterations before I worked out how to design
    it so the it could be invoked in a clean, simple way by a batch file.

    All this is to say that program design and refactoring can play a large
    role in writing code that can be understood relatively easily, follow
    the style guidelines as closely as possible, and be as easy as possible
    to maintain.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Cameron Simpson@21:1/5 to Thomas Passin on Sun Jan 22 10:50:01 2023
    On 21Jan2023 01:32, Thomas Passin <list1@tompassin.net> wrote:
    In another thread ("Improvement to imports, what is a better way ?")
    there was a lot of talk about line length, PEP-8, etc. I realized
    that one subject did not really come up, yet it can greatly affect the
    things we were talking about.

    I'm referring to the design of the functions, methods, and classes.
    When they are well designed, or more likely, refactored over and over
    again, they can lead to code that reads almost like pseudo-code. [...]

    Yeah. Personally, I summarise this as the maxim: "ergonomics matter".
    Code should be easy to use and easy to read.

    I've got a heap of code I've refactored after the initial implementation
    to make it easy to use, if only to make it easier for _me_ to use to do
    more things with it.

    I've also got decorators and context managers and special classes all
    aimed at making all the things easier to use and easier to _write_
    (because some decorators (a) provide flexibility for the caller - easier
    to use and (b) implement standard boilerplate logic for common types of parameters, making the function/method implementation easier to write.)

    And increasingly I've got docstrings with _example_ code which doubles
    as a doctest. Making it easier to understand how to easily use this
    thing.

    Cheers,
    Cameron Simpson <cs@cskk.id.au>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)