A habit I'm thinking of adopting for my shell scripting:

Write scripts in the form of a sequence of function definitions, with no commands at the top level that actually do anything. Then, at the very end, write a compound statement that calls one of the functions and ends with an exit. Like this:

#!/bin/bash
subroutine() { ... }
main() { ... }
{ main "$@"; exit $?; }

The idea is that the shell doesn't actually _do_ anything while reading this script until it reads the braced compound statement at the end. And then it's committed to exiting during the execution of that statement. So it won't ever read from the script file again.

With a normal shell script, it's dangerous to edit the script file while an instance of the shell is still running it, because the shell will read from the modified version of the file starting at the file position it had got to in the old one, perhaps reading a partial command or the wrong command and doing something you didn't want. But with a script in this style, the shell finishes reading the script before it does anything, so it's safe to edit.

(Of course, if your editor saves the new script file to a different file name and renames it over the top, you're safe anyway. But not all editors do: emacs, in particular, reopens the existing file and overwrites its contents.)

@simontatham I adopted this approach a while ago and am happy I did. Not least because it helps me organise my thoughts and also is a structural decision already made so one thing less to think about. Example https://github.com/qmacro/dotfiles/blob/main/scripts/strava-api/oauthflow-local
@qmacro I think I'd be more impressed with it as a means of organising yourself if shell functions didn't default to having global variable scope! The same structure in Python protects you against accidentally having a disorganised mess of global variables everywhere, but in shell you don't get that benefit unless you're _also_ careful to use 'local' all over the place.
@simontatham I need a LOT of organising :-)