treehouse/content/programming/opinions.tree
2024-07-24 18:20:12 +02:00

277 lines
13 KiB
Plaintext

%% title = "Hotland - Bad Opinion Zone"
% id = "01HBTSXTTAAAHGKD4TZZW14KFK"
- > "Hotland - Bad Opinion Zone"
% id = "01HBTSXTTAMWJ2BM6395YS72FN"
+ :page: log verbosity levels are stupid
% id = "01HBTSXTTA946PJ04NN50P393T"
- because when should you use any log level other than `error`, `warn`, or `info`?
% id = "01HBTSXTTAA873BYW2X3499W4P"
- for debug info that doesn't affect most users, there will always be lower verbosity levels
than `info`, but in my experience these are *always* extremely specific, and clumping them
all into `debug` or `trace` doesn't make much sense
% id = "01HBTSXTTAQ9S7Q1SSPN0BCG8F"
+ in that case I believe it is better to declare separate channels that can be filtered individually, and use `info` for everything
% id = "01HBTSXTTAHCQ5YH1FZT3WKNTZ"
- you say `info` sounds wrong for debug info? well,
% id = "01HBTSXTTATA4GW13QBC8YMWP1"
- guess what, it's debug _*info*_
% id = "01HBTSXTTANNMHXDGG1M4W70XN"
- the very concept of logs is to dump a load of (debug) info for later analysis,
so making some distinction between debug and non-debug info while logs are
_inherently_ for debugging seems a little silly to me
% id = "01HBTSXTTADJCTV9RTWT4NPASS"
+ use severities instead of verbosities
% id = "01HBTSXTTA3KN26DS4QMPSHEEC"
- `error` is "OH SHIT OH FUCK UHHH"
% id = "01HBTSXTTATK9W0TRGM50SKWRE"
- `warn` is "ding ding ding! something ain't right, but I'll carry on"
% id = "01HBTSXTTA8Z5JJHFKH4T88G0B"
- `info` is just that - info
% id = "01HBTSXTTANQXBN7FQQSBXTSB1"
- and there is really no need for anything lower, because guess what: [`debug` and `trace`](https://docs.rs/tracing/latest/tracing/struct.Level.html)
and [`Display` and `Verbose` and `VeryVerbose`](https://docs.unrealengine.com/4.26/en-US/API/Runtime/Core/Logging/ELogVerbosity__Type/)
and what have you - they're all just more `info` for you
% id = "01HBTSXTTAE94HEE60GZFBEAW7"
- and while writing logging code it's always hard to decide which info should be
stuffed into which verbosity, because which info will be interesting to you at the
time you read logs depends on what you're looking for at a given moment
% id = "01HBTSXTTAKZ3MKD3EJPX84J9N"
+ this is something I haven't tried yet, but one day I wanna build a logging framework one day that associates severities with whole categories rather than individual log messages
% id = "01HBTSXTTAD4TQ0A8JP71KZMXA"
- but at that point, do we even need severities at all? would a hierarchy of categories suffice?
% id = "01HBTSXTTAW160GH44JV3JYV3Y"
- I have no clue how well that will work but it would be pretty interesting
% id = "01HBTSXTTASQDZK2HMCZXR3JJ4"
+ :page: tracing is better than logging
% id = "01HBTSXTTA08SCA313HSFWB13T"
- information about time spans is very valuable for profiling
% id = "01HBTSXTTAAHQ0Q8GY78Z1RYFN"
- and you do not have to worry about "should I log before I start the operation, or after I end the operation?"
% id = "01HBTSXTTAJ6R319P2N6HT54CA"
+ additionally, traces made of spans are way easier to visualize than tonnes of logs
% id = "01HBTSXTTA3M93STVPHA898GM4"
- if you've never tried [Perfetto](https://ui.perfetto.dev/) or similar tools, I highly recommend giving it a shot
% id = "01HBTSXTTAB9WD17FNDE5RYP8Y"
+ I also imagine visualizing traces live in your CLI could yield a very nice user experience,
with a visible progress indicator as to what eg. [your compiler][branch:programming/projects/muscript]
is working on right now at a glance, reassuring you that it is not stuck on some
`while (true) {}` in constant evaluation
% id = "01HBTSXTTA3Q36Y98GSBRVCS3B"
- perhaps emitting warnings along the road for things that take alarmingly long, so that you
can keep your build times in check
% id = "01HBTSXTTAA89CXD17GBNR3FEQ"
- though printing to stdout is quite slow, so perhaps limiting the frequency or depth would
be a worthwhile thing to do
% id = "01HCD90XT3G0J8G9Y48H5QT1GJ"
+ :page: don't use [`RefCell<T>`](https://doc.rust-lang.org/std/cell/struct.RefCell.html)
% id = "01HCD90XT3X82R37WTJJABZF4Y"
+ usually if you have to resort to `RefCell<T>`, it means something is wrong with your architecture
% id = "01HCD90XT3QQA9MCB77Q04W148"
- `Cell<T>` less so but still you should avoid it whenever possible
% id = "01HCD90XT36Q0WTZNRCEWZQE1X"
- `Rc<RefCell<T>>` is an immediate red flag when I read someone's code. it means they don't
really know what they're doing and are just trying to sidestep the language
% id = "01HCD90XT3QNZ7H77Z4QMW66QJ"
- it's really not that hard to avoid, try bumping your resources up a scope and borrow them
from there
% id = "01HCD90XT32M4F61W5QRRRXG0K"
+ using `RefCell` has a few disadvantages
% id = "01HCD90XT3R214HQM03TSRZV6K"
+ first, `RefCell` is _literally_ turning off the borrow checker
% id = "01HCD90XT3V4K4RKRQXX5BCCRB"
- or really deferring it until runtime, but then what's the point. Rust's borrowing
rules are meant to prevent bugs, not cause more of them
% id = "01HCD90XT3DVTHP82Z5NAAF7RP"
+ second, your program can now panic in unexpected places, because it turns out runtime
borrows can be really hard to predict
% id = "01HCD90XT34QD8MCN0BA35AV0F"
- as evidenced by a random crash I once got while using [druid](https://lib.rs/crates/druid)
which was caused by a overlapping mutable then immutable borrows at runtime
% id = "01HCD90XT39NZ0BSY630MFNG1X"
- therefore maybe uhhh... don't use it in libraries? :pleading:
% id = "01HCD90XT3GT9G16EQDAK76WEC"
+ third, it incurs a runtime overhead for the borrow checking, which in 99% of cases is
totally unnecessary
% id = "01HCD90XT3V3B1TVPX7GS8SPDQ"
- (this is more about non-pessimization rather than premature microoptimization, so
don't yell at me with your "it'll be fast enough" arguments please)
% id = "01HCD90XT37138S38DN0V3DKHF"
+ fourth, and this one is extremely easy to spot - your code becomes really verbose!
% id = "01HCD90XT3WNGWD4GA2Y63D42H"
- yes please, `.borrow()` me everywhere!
% id = "01HCD90XT361QQB2YNFBY51N2A"
- and you end up with lots of temporaries because of the [`Ref<'b, T>`](https://doc.rust-lang.org/std/cell/struct.Ref.html)
guards you have to keep in scope.
% id = "01HCD90XT34K9BREXAKA5FFP3M"
- remember that you cannot do `&ref_cell.borrow().some_field` because that would
drop the temporary `Ref<'b, T>` guard after the expression ends
% id = "01HCG7KTGGAFS07QYJXZG6WHJJ"
+ :page: `None` or `Invalid` cases in enums are a bad idea
% id = "01HCG7KTGGHWTT1ME9GQ5VPFPR"
- by having them, you're forcing your users into an API that forces them to think about the
invalid case every time they read the value.
% id = "01HCG7KTGGBJRX5JGQTFD59P4W"
- you're repeating the million dollar mistake - `null`
% id = "01HCG7KTGGYQ8EQB97AMFJPWYK"
- instead, use an explicit `Option<T>` or `std::optional<T>` or `T?` or ... when you need
to represent a possibly-invalid case
% id = "01HPEMVAH9JZWYPVN53GVFQNQY"
+ :page: NaNs should crash the program
% id = "01HPEMVAH97PDHJJS70SKG5VMN"
- any time I see a NaN I cry inside knowing how much pain debugging it's gonna be.
% id = "01HPEMVAH9Y3W35Y6Z4QMCJ5QM"
- I'd rather have the program crash and burn at the point a `NaN` is produced rather than have to sift through all the math to find that one division by zero I didn't account for
% id = "01HPEMVAH9XG3RK62RFXD29RWV"
- this does influence performance negatively, but it saves _so much_ debugging pain and finding out which non deterministic scenario causes a NaN to propagate through the system
% id = "01HPEMVAH9CKAEQBMC8S6MR0GQ"
- worst case scenario you pull a Rust and disable those checks on release mode. that _does_ work, but I don't like the idea of disabling numeric safety checks on release mode either.
% id = "01HPEQ01JRMM17Y30BP7ZFKZRJ"
+ :page: operator overloading is good, but getters and setters are not
% id = "01HPEQ01JR57B057439SY90BQ9"
- this one stems from an argument I had today, so I'll write my thoughts for future generations' enjoyment here
% id = "01HPEQ01JR4YWC9Q6VYS82J0E3"
- I'll start by prefacing that I think operator overloading is good [_iff_][def:word/iff] it's implemented in a way that a single operator has only one, well-defined meaning
% id = "01HPEQ01JRBB8Z3P0KFJSR0SJN"
- this means `+` really means _addition_ and nothing else.
% id = "01HPEQ01JRJJBP9C701B36ZR4N"
- this is practically impossible to enforce at a language level - what prevents the standard library authors from overloading `+` to mean string concatenation after all?
% id = "01HPEQ01JRY7R5QGJ2AM762PPN"
- however we can at least do our best by writing good defaults and coding standards that gently suggest what to do and what not to do
% id = "01HPEQ01JR4ZC0M68818EDVDBF"
- for example, allow users to define their own arbitrary operators that are explicitly _not_ addition, to incentivize inventing new syntax for these things
% id = "01HPEQ01JRTWHH6PVNTFBDXPVT"
- the way I'd like to do it in [my dream language][def:rokugo/repo] is by a few means
% id = "01HPEQ01JRAAK5MQCZ7CFZ75FA"
- `(+)` is defined to be a polymorphic operator which calls into a module implementing the `AddSub` interface, which means you have to implement both addition _and_ subtraction for `(+)` to work on your type
```rokugo
let AddSub = interface {
type T
fun add (a : T) (b : T) : T
fun subtract (a : T) (b : T) : T
}
fun (+) (a : let T) (b : T) : T
use AS : AddSub with { T } =
AS.add a b
```
% id = "01HPEQ01JR71RV53NNSFFDV6XN"
- note how this operator _does not_ have any effects declared on it - this means addition and subtraction must not have any side effects such as I/O
% id = "01HPEQ01JRJR3ZAY24BP8TF5HH"
+ the `(add AND subtract)` rule enforces types like strings to take a different operator, because `(-)` does not have a well-defined meaning on strings
% id = "01HPEQ01JRGCPT2PGY5HK7HK7F"
- is `"foobar" - "bar" == "foo"`?
% id = "01HPEQ01JR3CVNNACZ6EGQ7NWM"
- by extension, is `"foofoobarbar" - "bar" == "foofoobar"` or `"foofoobarbar" - "bar" == "foofoo"`?
% id = "01HPEQ01JRK25NHG72ZX5XHEEJ"
- maybe characters are subtracted from the left string one by one? such that `"foobar" - "bar" == "\x04\x0e\xfcbar"` (wtf)
% id = "01HPEQ01JR25J5BY54J6RJ0KEC"
- so now getters and setters: what's so bad about them?
% id = "01HPEQ01JRQPZJEDDXV4BJN1GP"
- the problem is that given the rule above - _one operator means one thing_ - getters and setters completely destroy your assumptions about what `=` might do
% id = "01HPEQ01JR0E8C0VJZ1D9TJRAG"
- what's that? you didn't expect `camera.angle_z = 420` to throw because 420 is out of the `[-π/2, π/2]` range? oops!
% id = "01HPEQ01JR0T4C2YC7TE9ZHXHT"
- what's that? you didn't expect `camera.angle_z` to return a different value every time you access it? oh, well!
% id = "01HPEQ01JR2KWGJVP7T4SH1SXD"
- at least when it's spelled `camera.angle_z()` it suggests that it might do something weird, like access the thread RNG.
% id = "01HPEQ01JRDNA35YPYV30CJG42"
- not to mention all the infinite recursion annoyance that sometimes happens when implementing them manually
% id = "01HPEQ01JRQFSFVPQA41MFZ91T"
- this is less of a problem in languages that feature automatic generation of getters and setters - such as Kotlin
```kotlin
var someVariable: String
get
private set
// no infinite recursion to be seen here!
```
but it's still an issue in e.g. JavaScript, where one mistake can send your call stack down the spiral:
```javascript
class Example {
#someVariable = "";
get someVariable() { return this.someVariable; } // typo!!!!
set someVariable(value) { this.someVariable = value; } // typo again!!!!!!!!!! dammit!
}
```
and the error is not caught until runtime.
% id = "01HPEQ01JRMMS1B400DP6DV5M9"
- it's easy to fix but still an annoyance whenever you write a getter/setter pair.