3 ms·
The counterpoint in software is that it's still useful to temporarily solve a narrow problem - and never need to return to that issue. How do you denote that in
by creer 3y ago
The counterpoint in software is that it's still useful to temporarily solve a narrow problem - and never need to return to that issue. How do you denote that in your code?
I have used multiple dispatch where a sub-type is deliberately handled, and the rest is left with "Nice try, you would have to implement that first", and tests in library functions with the same rude response. And I have used strictly defined grammars on the input. ... and I still have lots of TODO list items not correctly protected from in the code. What else do you use in non-trivial cases? Much deeper or beyond program argument checks?
- beebmam 3y agoWhat I do: 1. Add TODO items when I'm in flow 2. When I'm not in flow, I search TODO items in code/notes I own 3. Add a basic task in a project management tool with no estimations for each TODO item At least then it's in a system to be triaged at some point.
- nomel 3y agoFor me TODO is more literal, reserved for things that must be done. So, I use SMELL if it's unsane. If it's somewhat broken, I use BUG, and file a low priority issue. For both, I put long explanations, in the code, for the future reader.
- JohnFen 3y agoI use a combination of things, depending on the nature of the temporary measure. I use TODO comments to document that this part of the code needs to be revisited. I write a warning to the log file whenever the temporary code executes. If there are things left unimplemented, but there's an interface to them, then I put code in that's guaranteed to fail at runtime. If the language is C or C++, I'll use something like assert(false).
- hansvm 3y agoYou make your life substantially easier if all the functions/methods/procedures/blocks/... in your code are "total" -- if they transform every possible input into the output one would expect from the name and type signature. With that as a guiding principle, I tend toward the following strategies, the particular choice of which depends on other details. Most often I'll combine 2/5, throwing a compile-time error for the broken branches, later realizing that the abstraction I chose was stupid, and then writing code that actually does what I want. 1. Inline the work being done. If you've only implemented a single case it doesn't deserve a named function that does something different from what the name indicates. Refactor it later if you ever need when you have more cases. 2. Ensure the error happens at compilation. C# has diagnostic directives. Zig has the @compileError builtin. C/C++ can fail using the preprocessor. It's a relatively common feature, and if you ensure that when your buggy/unfinished function compiles it's correct then that mitigates the harm you might cause (and serves as documentation: @compileError("TODO: ...")). 3. Ensure the type signature captures the problem. If some inputs result in crashes, return an error-code/error-condition/failure-union/optional/nullable/... so that the caller knows shit might happen and can deal with it at runtime if it does. 4. Make it abundantly clear that there's an issue. Name it `unsafe_unfinished_only_works_for_this_subtype_foo_bar` or something similarly onerous. 5. Use a different abstraction. Rather than `def handles_everything(something_that_might_not_be_handled):` -- a great abstraction that you don't actually use yet because you don't actually handle everything, instead write a `def handles_something(something):` function. Refactoring is cheap. If you don't need the full abstraction then don't use it, and definitely don't write an unfinished, buggy version of the abstraction you're not using. 5a. It might be hard to write the type signature of that restricted "something". E.g., let's say your function only works on sorted lists. A wrapper type pointing to the thing you actually want can be a good tool for this. E.g., you might have a SortedList type, and it might expose two methods for instantiating it -- validate_sorted (which might return an error but which checks that the list is actually in order), and unsafe_assert_sorted (where you as a programmer pinky promise that you know what you're doing and create the wrapper object regardless). Any function doing a binary search can now rely on the SortedList behaving appropriately, and all bugs associated with that function call can be tied to the very greppable name "unsafe_assert_sorted". Every possible input to the function yields an appropriate output. I'm not the first person to notice this idea. [0] 6. Limit the visibility. Use inner methods, inner classes, private, protected, underscore prefixes, or whatever else your language has to indicate to the outside world that they're technically allowed to use this method but that they probably want to think twice about it (similarly to the "unsafe_unfinished_..." from above, but also tends to play nicely with tooling). 7. Just finish the thing. Shaky foundations are more expensive than they appear, and when you've written one case it's actually usually pretty fast to implement the rest, especially while you have all the surrounding context in your mind. 8. Ignore all of the above. Sometimes something like performance overrides other concerns. Code is a balancing act. [0] https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
- creer 3y agoSo yes, there is a fundamental difference in approach between a pessimistically (?) strongly typed environment and an optimistically loosely typed one. Okay. Good point. I find myself writing with an optimistic more general thinking (so more general typing choices and architecture) but where I know that the code that I never have to write is the most efficient one. Heh. Or perhaps where I could have spent the time to write safer, narrower classes but couldn't be bothered. THEN I have to make the resulting working program safer before later foot-shooting situations. And the thing is in use so compile-time errors are not allowed (in this style of thinking. "assert" was mentioned earlier. Useful and clean and greppable abstration, yes! Raku has interesting options for types and signatures that I have not yet abused, like: multi sub doesnt_handle_everything ( Int $n where {$_ < 5} ) { etc; }; or: subset Monster of Str where { $_ eq any( <godzilla gammera ghidora> ) };