[HN Gopher] C Style: My favorite C programming practices (2014)
___________________________________________________________________
C Style: My favorite C programming practices (2014)
Author : zerojames
Score : 198 points
Date : 2024-05-19 21:39 UTC (1 days ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| a_e_k wrote:
| I feel like I probably agree with about 80% of this. It also
| seems like this would apply fairly well to C++ as well.
|
| One thing that I'll strongly quibble with: "Use double rather
| than float, unless you have a specific reason otherwise".
|
| As a graphics programmer, I've found that single precision will
| do just fine in the vast majority of cases. I've also found that
| it's often better to try to make my code work well in the single
| precision while keeping an eye out for precision loss. Then I can
| either rewrite my math to try to avoid the precision loss, or
| selectively use double precision just in the parts where its
| needed. I think that using double precision from the start is a
| big hammer that's often unneeded. And using single precision buys
| you double the number of floats moving through your cache and
| memory bandwidth compared to using double precision.
| amszmidt wrote:
| The one about not using 'switch' and instead using combined
| logical comparisons is terrible ... quite opinionated, but that
| is usually the case with these type of style guides.
| aulin wrote:
| it's like they purposely add some controversial rule just for
| engagement
| ezconnect wrote:
| He even uses 'switch' on his code.
| mcinglis wrote:
| As the author 10 years later, I agree. A hard-and-fast rule
| to ban switch, as that rule seems to advocate, is silly and
| terrible.
|
| Switch has many valid uses. However, I also often see switch
| used in places where functional decomposition would've been
| much better (maintainable / testable / extensible). So I
| think there's still value in advocating for those switch
| alternatives, such as that rule's text covers. Not that I
| agree with everything there either. But, useful for
| discussion!
| lifthrasiir wrote:
| I think the fact that graphics care a lot more about efficiency
| over marginal accuracy qualifies for a specific reason. Besides
| from that and a few select areas like ML, almost any reason to
| use `float` _by default_ vanishes.
| ack_complete wrote:
| I'm torn both ways on the double issue. On the one hand,
| doubles are much more widely supported these days, and will
| save you from some common scenarios. Timestamps are a
| particular one, where a float will often degrade on a time
| scale that you care about, and doubles not. A double will also
| hold any int value without loss (on mainstream platforms), and
| has enough precision to allow staying in world coordinates for
| 3D geometry without introducing depth buffer problems.
|
| OTOH, double precision is often just a panacea. If you don't
| know the precision requirements of your algorithm, how do you
| know that double precision will work either? Some types of
| errors will compound without anti-drifting protection in ways
| that are exponential, where the extra mantissa bits from a
| double will only get you a constant factor of additional time.
|
| There are also current platforms where double will land you in
| very significant performance problems, not just a minor hit.
| GPUs are a particularly fun one -- there are currently popular
| GPUs where double precision math runs at 1/32 the rate of
| single precision.
| teddyh wrote:
| I think you used the word "panacea" incorrectly. Judging by
| context, I would guess that the word "band-aid" would better
| convey your intended meaning.
| nine_k wrote:
| A "panacea" is something that cures.every illness. 64-bit
| floats could do just that, in the cases listed. The cost of
| it may be higher than one cares to pay though.
|
| And when the cure fails to be adequate, well, it becomes a
| band-aid, a temporary measure in search of a real solution.
| zbentley wrote:
| Perhaps "placebo" was intended?
| a1369209993 wrote:
| IIUC, they're using the word itself correctly, but they
| mean "double precision is often _used with the intent of it
| being_ a panacea ".
| orthoxerox wrote:
| Are there C++ libs that use floating points for timestamps? I
| was under the impression that most stacks have accepted int64
| epoch microseconds as the most reasonable format.
| gpderetta wrote:
| > int64 epoch microseconds
|
| surely nanoseconds is the truth.
| orthoxerox wrote:
| LONG_MAX nanoseconds is just Friday, April 11, 2262
| 11:47:16.854 PM, not exactly a future-proof approach. I
| guess having Tuesday, September 21, 1677 12:12:43.145 AM
| as the earliest expressible timestamp neatly sidesteps
| the problem of proleptic Gregorian vs Julian calendars.
| Const-me wrote:
| I think Apple is still doing that: https://developer.apple.
| com/documentation/foundation/nstimei...
|
| Couple decades ago Microsoft did that too, VT_DATE in old
| OLE Automation keeping FP64 value inside. Luckily, their
| newer APIs and frameworks are using uint64 with
| 100-nanoseconds ticks.
| ack_complete wrote:
| Don't have a publicly visible reference to give at the
| moment, but it's still sometimes seen where relative
| timestamps are being tracked, such as in an audio library
| tracking time elapsed since start. It's probably less used
| for absolute time where the precision problems are more
| obvious.
| e4m2 wrote:
| It's very common in games.
|
| Integers are always an option, of course, but in this
| context it's hard to beat the convenience of just storing
| seconds in a floating point number.
|
| Related: https://randomascii.wordpress.com/2012/02/13/dont-
| store-that...
| sdk77 wrote:
| There are popular embedded platforms like STM32 that don't have
| hardware double support, but do have hardware float support.
| Using double will cause software double support to be linked
| and slow down your firmware significantly.
| AnimalMuppet wrote:
| OK, but if you're writing for that kind of platform, you know
| it. Don't use double _there_? Sure. "Don't use double on
| non-embedded code just because such platforms exist" doesn't
| make sense to me.
|
| Sure, my code could maybe run on an embedded platform
| someday. But the person importing it probably has an editor
| that can do a search and replace...
| JKCalhoun wrote:
| > I feel like I probably agree with about 80% of this.
|
| What I was thinking too. There's something in here to offend
| everyone, and that's probably a good thing.
| projektfu wrote:
| The issue for me is that unlabeled constants are doubles and
| they can cause promotion where you don't expect it, leading to
| double arithmetic and rounding instead of single arithmetic.
| Minor issue, but hidden behavior.
| atiedebee wrote:
| What's even more annoying is that the *printf functions take
| in double which forces you to cast all of the floats you pass
| in when using -Wdouble-promotion
| Lvl999Noob wrote:
| I think your case comes under the "specific reason to use
| `float`"? If I am writing some code and I need floating point
| numbers, then without any more context, I will choose `double`.
| If I have context and the context makes it so `float`s are
| vastly better, then I will use `float`s.
| liblfds-temp wrote:
| Declare all variables/qualifiers right-to-left.
|
| Read the type for all the below right-to-left, substituting the
| word "pointer" for "*". int long long unsigned
| wibble; // unsigned long long int double const
| *long_number; // pointer to a const double double volatile
| * const immutable_pointer; // immutable pointer to a volatile
| double
|
| They all read correctly now, when read right-to-left. It's not
| just "const" you do this for, as per the advice. Do it for all
| qualifiers.
| mrkeen wrote:
| What's the author's justification? What's your justification?
|
| > They all read correctly now, when read right-to-left.
|
| ... suppose I'm someone who reads from left-to-right, should I
| flip the order to make it correct for me?
| liblfds-temp wrote:
| Readability.
|
| C declarations can become unfriendly by being too complex and
| disordered.
| wruza wrote:
| Nothing seems wrong with "volatile double pointer as a
| constant" or "constant character pointer" either, tbh. The
| way you presented is equivalent, but non-idiomatic, people
| would stumble upon it often. To become more readable
| universally this must have been adopted 50 years ago.
| Someone wrote:
| > What's the author's justification? What's your
| justification?
|
| I'm neither of them, but chances are that's because you can't
| make them left-to-right all the time. double
| const *foo; // foo is a pointer to a const double
| double *const foo; // foo is a const pointer to a double
|
| compile and do what the comment says; these do not compile:
| * const double foo; // a pointer to a const double named
| "foo" foo * const double; // foo is a pointer to a
| const double
| spc476 wrote:
| I use the "right-to-left" style myself. To me, the
| qualifier (in this case, const), applies to the item to the
| right. This could be confusing: const
| char *const ptr;
|
| The first const applies to the char, but the second one to
| the pointer itself. Being consistent:
| char const *const ptr;
|
| The first const applies to the item to its left---char. The
| second const applies to the item to its left---the pointer.
| To recap: char *ptr1; // modifiable
| pointer to modifiable data char const *ptr2; //
| modifiable pointer to const data char *const ptr3;
| // const pointer to modifiable data char const
| *const ptr4; // const pointer to const data
| Y_Y wrote:
| * doesn't (have to) mean "pointer"!
|
| It works in simple cases, but I find the consistent thing to do
| is read it as "dereference". double volatile
| *(const immutable_pointer); // immutable_pointer is
| immutable, when you dereference it you'll get a volatile double
| planede wrote:
| Yes, that's the philosophy around the declaration syntax.
|
| _The declaration of the pointer ip,_ int
| *ip;
|
| _is intended as a mnemonic; it says that the expression *ip
| is an int. The syntax of the declaration for a variable
| mimics the syntax of expressions in which the variable might
| appear. This reasoning applies to function declarations as
| well._
|
| K&R C
| robxorb wrote:
| > Write correct, readable, simple and maintainable software, and
| tune it when you're done, with benchmarks to identify the choke
| points
|
| If speed is a primary concern, you can't tack it on at the end,
| it needs to be built in architecturally. Benchmarks applied after
| meeting goals of read/maintainability are only benchmarking the
| limits of that approach and focus.
|
| They can't capture the results of trying and benchmarking several
| different fundamental approaches made at the outset in order to
| best choose the initial direction. In this case "optimisation" is
| almost happening _first_.
|
| Sometimes the fastest approach may not be particularly
| maintainable, and that may be just fine if that component is not
| expected to require maintaining, eg, a pure C bare-metal in a
| bespoke and one-off embedded environment.
| f1shy wrote:
| That was my way of thinking as I was junior programming.
| itishappy wrote:
| And now...?
| queuebert wrote:
| They're a manager and send out daily emails reminding the
| coders of arbitrary deadlines.
| f1shy wrote:
| Who they?!
| f1shy wrote:
| After being burn waaay too many times with one of: 1) write
| only code (for the sake of "speed" 2) optimization of the
| wrong piece of code
|
| I do think it is much better to prioritize readability;
| then measure where the code has to be sped up, and then do
| changes, but try _HARD_ to first find a better algorithm,
| and if that does not work, and more processor, or equipment
| is not viable or still does not work, go for less readable
| code, which is microoptimized
| vbezhenar wrote:
| I don't know if this embedded development still alive. I'm
| writing firmware for nRF BLE chip which is supposed to run from
| battery and their SDK uses operating system. Absolutely
| monstrous chips with enormous RAM and Flash. Makes zero sense
| to optimize for anything, as long as device sleeps well.
| robxorb wrote:
| Probably right in the broader sense, but there are still
| niches. Eg, for one: space deployments, where sufficiently
| hardened parts may lag decades behind SOTA and the environ
| can require a careful balance of energy/heat against run-
| time.
| mark_undoio wrote:
| A little over 10 years ago I was doing some very resource-
| constrained embedded programming. We had been using custom
| chip with an 8051-compatible instruction set (plus some
| special purpose analogue circuitry) with a few hundred bytes
| of RAM. For a new project we used an ARM Cortex M0, plus some
| external circuitry for analogue parts.
|
| The difference was ridiculous - we were actually porting a
| prototype algorithm from a powerful TI device with hardware
| floating point. It turned out viable to simply compile the
| same algorithm with software emulation of floating point -
| the Cortex M0 could keep up.
|
| Having said all that though: the 8051 solution was _so much_
| physically smaller that the ARM just wouldn 't have been
| viable in some products (this was more significant because
| having the analogue circuitry on-chip limited how small the
| feature size for the digital part of the silicon could be).
|
| Obviously that was quite a while ago! But even at the time, I
| was amazed how much difference the simpler chip made actually
| made to the size of the solution. The ARM would have been a
| total deal breaker for that first project, it would just have
| been too big. I could certainly believe people are still
| programming for applications like that where a modern CPU
| doesn't get a look in.
| ajross wrote:
| It's still alive, but pushed down the layers. The OS kernel
| on top of which you sit still cares about things like
| interrupt entry latency, which means that stack usage
| analysis and inlining management has a home, etc... The
| bluetooth radio and network stacks you're using likely has
| performance paths that force people to look at disassembly to
| understand.
|
| But it's true that outside the top-level "don't make dumb
| design decisions" decision points, application code in the
| embedded world is reasonably insulated form this kind of
| nonsense. But that's because the folks you're standing on did
| the work for you.
| kragen wrote:
| i just learned the other day that you can get a computer for
| 1.58C/ in quantity 20000:
| https://jlcpcb.com/partdetail/NyquestTech-NY8A051H/C5143390
|
| it's basically a pic12f clone (with 55 'powerful'
| instructions, most single-cycle) with 512 instructions of
| memory, a 4-level hardware stack, and 32 bytes of ram, with
| an internal 20 megahertz clock, 20 milliamps per pin at 5
| volts, burning half a microamp in halt mode and 700 microamps
| at full speed at 3 volts
|
| and it costs less than most discrete transistors
|
| there are definitely a lot of things you can do with this
| chip if you're willing to optimize your code. but you aren't
| going to start with a 30-kilobyte firmware image and optimize
| it until it fits
|
| yeah it's not an nrf52840 and you probably can't do ble on
| it. but the ny8a051h costs 1.58C/, and an nrf52840 costs
| 245C/, 154 times as much, and only runs three times as fast
| on the kinds of things you'd mostly use the ny8a051h for. it
| does have a lot more than 154 times as much ram tho
| eschneider wrote:
| Well, yes. Architect for performance, try not to do anything
| "dumb", but save micro-optimizations for after performance
| measurement.
| ragnese wrote:
| The problem with all of these rules of thumb is that they're
| vague to the point of being vacuously true. Of course we all
| agree that "premature optimization is the root of all evil"
| as Knuth once said, but the saying itself is basically a
| tautology: if something is "premature", that already means
| it's wrong to do it.
|
| I'll be more impressed when I see specific advice about what
| kinds of "optimizations" are premature. Or, to address your
| reply specifically, what counts as "doing something dumb" vs.
| what is a "micro-optimization". And, the truth is, you can't
| really answer those questions without a specific project and
| programming language in mind.
|
| But, what I do end up seeing across domains and programming
| languages is that people sacrifice efficiency (which is
| objective and measurable, even if "micro") for a vague idea
| of what _they_ consider to be "readable" (today--ask them
| again in six months). What I'm specifically thinking of is
| people writing in programming languages with eager collection
| types that have `map`, `filter`, etc methods, and they'll
| chain four or five of them together because it's "more
| readable" than a for-loop. The difference in readability is
| absolutely negligible to any programmer, but they choose to
| make four extra heap-allocated, temporary, arrays/lists and
| iterate over the N elements four or five times instead of
| once because it looks _slightly_ more elegant (and I agree
| that it does). Is it a "micro-optimization" to just opt for
| the for-loop so that I _don 't_ have to benchmark how shitty
| the performance is in the future when we're iterating over
| more elements than we thought we'd ever need to? Or is it not
| doing something dumb? To me, it seems ridiculous to
| intentionally choose a sub-optimal solution when the optimal
| one is just as easy to write and 99% (or more) as easy to
| read/understand.
| eschneider wrote:
| Ok, a bit more detail then. :)
|
| Architecting for performance means picking your data
| structures, data flow, and algorithms with some thought
| towards efficiency for the application you have in mind.
| Details will vary a lot depending on context. But as many
| folks have said, this sort of thing can't be done after the
| fact.
|
| As for "doing something dumb", I've often seem fellow
| engineers do things like repeatedly insert into sorted data
| structures in a loop instead of just inserting into an
| unsorted structure and then sorting after the inserts. If
| you think about it for just a minute, it should be obvious
| why that's not smart (for most cases.) Stuff like that.
|
| What do I mean by "micro-optimizations"? Taking a clearly
| written function and spending a lot of time making it as
| efficient _as_possible_ (possibly at the expense of
| clarity) without first doing some performance analysis to
| see if it matters.
|
| Nobody's saying to pick suboptimal solutions at all.
| wruza wrote:
| _Treat 79 characters as a hard limit_
|
| Try pasting a long URL into a comment describing a
| method/problem/solution and you'll see immediately that it
| doesn't fit 77 chars and you cannot wrap it. Then due to your
| hard limit you'll invent something like "// see explained.txt:123
| for explanation" or maybe "https://shrt.url/f0ob4r" it.
|
| There's nothing wrong with breaking limits if you do that
| reasonably, cause most limits have edge cases. It's (Rule -> Goal
| X) most of the times, but sometimes it's (Rule -> Issue). Make it
| (Solution (breaks Rule) -> Goal X), not (Solution (obeys Rule) ->
| not (Goal X)).
| kleiba wrote:
| Agree. This 80 character limit stems from a time where
| terminals could only display comparatively few characters in a
| line, a limit we haven't had in decades as screen resolutions
| grew.
|
| Another argument for shorters lines is that it is much harder
| for us to read any text when lines get too long. There's a
| reason why we read and write documents in portrait mode, not
| landscape.
|
| But in sum, I don't think there's a need for creating a hard
| limit at the 80 character mark. Most code is not indented more
| than three or four times anyways, and most if not all languages
| allow you to insert newlines to make long expressions wrap.
| However, if you occasionally _do_ need to go longer, I think
| that 's completely fine and certainly better than having to
| bend around an arcane character limit.
| f1shy wrote:
| > This 80 character limit stems from a time where terminals
| could only display comparatively few characters in a line, a
| limit we haven't had in decades as screen resolutions grew.
|
| The 80 char rule has little to do with old monitors. Has to
| do with ergonomics, and is why any good edited and typeset
| book will have between 60 and 80 characters per line.
| jjgreen wrote:
| Exactly this. Open a novel and count the characters on a
| line; around 80 is _readable_ as 500 years of typographic
| practice has determined. Two or three levels of indentation
| and that bumps the page width up a bit, still less than
| 100.
| ykonstant wrote:
| It is a fair point, but a book is 60 - 80 characters of
| dense prose line after line in thick paragraphs; it is not
| clear how this translates to lines of code.
| f1shy wrote:
| In my personal experience (and of couse very subjective)
| it helps me a lit to have lines that fit the monitor, and
| I can have 2 parallel windows. Using 120 chars is for me
| just too much. I do think the golden rule of typography
| is "all rules can be broken if you know what you are
| doing". For me is a soft limit. If splitting a line makes
| the code less readable, I do allow more. But frankly,
| that is the case of one in maybe 50k LOC
| abraae wrote:
| 80 column punched cards were a very strong influence
| wruza wrote:
| As a part of ergonomics auditory, I really prefer 100-115
| column soft limit for code editors, log viewing and
| console, because that's how my single display dev setup
| works best. Otoh if I'm using IDEs with big sidebars like
| {X,VS}Code, then I need two displays and/or a full-width
| IDE anyway.
|
| While I understand that this is an anecdotal preference, to
| me it doesn't feel like the 80 column standard fits any
| modern dev workspace perfectly, tbh. (By modern I don't
| mean "shiny", just what we have now in hw/sw.)
| jeffdaniels27x wrote:
| Then let it be 80 characters from any whitespace on the
| left-hand side? I find it artificially awkward to have to
| wrap at a hard margin on the right-hand side. Surely you
| need to accommodate any indenting you're doing?
| f1shy wrote:
| Take for example man pages, I find the very comfy to
| read. And they have generous margins on both sides. About
| indenting, I try to avoid deep nesting, usually 3 is a
| maximum, very rare to need more, if the code has to be
| easy to read.
| nine_k wrote:
| Monitors are too new. Punch cards have 80 columns. I think
| this even pre-dates the use of teletypes with electronic
| computers.
| kleiba wrote:
| It has to do with both and that's why my comment mentions
| both.
|
| But then again, of course there is a _reason_ why terminals
| (or punchcards) were made that way - presumably _because
| of_ reading / writing ergonomics (besides technical
| reasons).
| jayd16 wrote:
| This rules readme isn't even less than 80 characters per
| line. You're saying we all had trouble reading it?
| lifthrasiir wrote:
| And at the very least, "80-characters-per-line is a de-facto
| standard for viewing code" has been long wrong. As the post
| even mentions, 100 and 120 columns have been another popular
| choices and thus we don't really have any de-facto standard
| about them!
| vbezhenar wrote:
| My opinion is that line width depends on identifier naming
| style.
|
| For example Java often prefers long explicitly verbose names
| for class, fields, methods, variables.
|
| Another approach is to use short names as much as possible.
| `mkdir` instead of `create_directory`, `i` instead of
| `person_index` and so on.
|
| I think that max line length greatly depends on the chosen
| identifier naming style. So it makes sense to use 100 or 120
| for Java and it makes sense to use 72 for Golang.
|
| C code often use short naming style, so 72 or 80 should be
| fine.
| lifthrasiir wrote:
| And you risk a collision for global identifiers, which
| cannot be reorganized in C. If some library had the same
| thought and defined `mkdir` first, your code can't define
| `mkdir` even as a private symbol. So you have to prefix
| everything and that contributes to the identifier length.
| In comparison, Go has a much better (but personally not yet
| satisfactory) module system and can have a lower limit.
| wruza wrote:
| Also on age, e.g. I began to prefer grandma font size from
| time to time for better focus, and that changes my
| workspace geometry.
|
| For mkdir all-in, meh. It's okay for mkdirp or rimraf,
| cause these basically became new words. But once you add
| more libs or code to a project it becomes a cryptic mess of
| six-letter nonsense. English code reads best, Java just
| overdoes it by adding patterns into the mix.
| kllrnohj wrote:
| C is the worst at naming length since everything is in the
| global namespace unless you're working on a teeny tiny
| project. So everything gets clunky prefixes to use as
| pseudo-namespaces or overly descriptive name to avoid
| conflicts.
|
| Local variables sure, be short and terse. But that's common
| in most languages.
| f1shy wrote:
| No rule is absolute. So you may have some line longer. Anyway
| I'm VERY skeptic that hardcoding URLs is a good idea at all.
| wruza wrote:
| Only hardcore cool URLs!
| ReleaseCandidat wrote:
| > Anyway I'm VERY skeptic that hardcoding URLs is a good idea
| at all.
|
| They are talking about URLs in comments.
| f1shy wrote:
| Oh. For comments I think would be ok, if it is a comment by
| itself, and not needed to particularly understand a piece
| of code, like in a headers. Still links are much more
| volatile than the codebases I work with. But I understand
| that may not hold in many many others setups.
| Scarbutt wrote:
| I doubt that rule applies to pasting URLs in comments. It's
| about code.
| marhee wrote:
| > developers have a hope of being able to determine which
| #includes can be removed and which can't
|
| Can't a modern compiler do that already? Didn't google but seems
| an obvious compiler feature at the very least behind a warning
| flag.
| vbezhenar wrote:
| Clang-tidy (linter) can do that. IMO it's a good idea to
| integrate this tool to any C project. I'm using gcc for
| embedded projects and clang-tidy works just fine as a separate
| tool.
|
| https://clang.llvm.org/extra/clang-tidy/checks/misc/include-...
| blame-troi wrote:
| I'm using https://github.com/include-what-you-use/include-what-
| you-use in preference over clang-tidy.
| LeoNatan25 wrote:
| > 80-characters-per-line is a de-facto standard for viewing code.
| Readers of your code who rely on that standard, and have their
| terminal or editor sized to 80 characters wide, can fit more on
| the screen by placing windows side-by-side.
|
| This is one of the silliest practices to still be enforced or
| even considered in 2024. "Readers" should get a modern IDE/text
| editor and/or modern hardware.
| vbezhenar wrote:
| I'm using modern IDE and 32" 4K display yet I still support
| this rule. One example where it's particularly convenient is
| 3-way merge. Also if we're talking about IDE's, they often use
| horizontal space for things like files tree (project explorer)
| and other tool windows.
| masklinn wrote:
| And on a wide display it's very convenient to use the width
| to put useful ancillary content on there (e.g. docs, company
| chat, ...). I shouldn't waste half my display on nothing
| because you won't add line breaks to your code.
|
| Annoyingly lots of modern website have very wonky breakpoints
| / detection and will serve nonsense mobile UIs on what I
| think is reasonable window widths e.g. if you consider
| bootstrap's "xl" to be desktop then an UWQHD display
| (3440x1440) won't get a desktop layout in 3 (to say nothing
| of 4) columns layouts, nor may smaller laptops (especially if
| they're zoomed somewhat).
| hkwerf wrote:
| The part you quoted has the one argument against yours right at
| the end. It's not about hardware or IDEs or text editors, it's
| about workspace layout.
| Keyframe wrote:
| au contraire! considering programming involves a lot of
| reading, it overlaps (or even comes from) with. best practices
| from ye olde tradition of typesetting https://en.m.wikipedia.or
| g/wiki/Line_length#:~:text=Traditio.... Aside books and print
| magazines and newspapers, we still respect that on web sites
| when reading is involved, why should programming be exempt of
| ergonomy?
| wruza wrote:
| _programming involves a lot of reading_
|
| Is that true for an average developer, really? Yes, we read
| lots of manuals, snippets, stackoverflows. But code? One does
| mostly write code.
|
| And when we do read code, it may lack good naming, structure,
| comments, clarity, may be unnecessarily complex or hacky.
| Where does it wrap is the thing one would care about only in
| perfect code, if at all. Most editors can smart-wrap and
| clearly indicate it anyway.
| naasking wrote:
| > Is that true for an average developer, really? Yes, we
| read lots of manuals, snippets, stackoverflows. But code?
| One does mostly write code.
|
| No, every developer almost certainly reads a lot more code
| than they write. You can't modify code to add a feature
| without reading and understanding the code first. The code
| you add is often very short compared to the code you need
| to read to understand what to modify.
| jonathanstrange wrote:
| There is a reason why books have only between 45 to 75
| characters per line. It greatly enhances readability.
| flohofwoe wrote:
| The rule is a bit silly sure, but OTH I typically have multiple
| editors tabs open side by side (I don't restrict myself to a
| hard 80 char line width though, but I have vertical rulers set
| at 80 and 120 characters in the editor as visual guidance).
| xipix wrote:
| A proportional font really helps ergonomics too.
| LeoNatan25 wrote:
| This is probably a snarky reply, but here is the serious
| answer: proportional fonts, with appropriate kerning, is a
| lot more legible than monospaced font. There is a reason why
| the press moved into that direction once it was technically
| feasible. But the same people that bring books as an example
| why 80 character line length should be enforced would gag at
| the notion of using proportional fonts for development. It
| just goes to show that none of these things actually matter,
| it's just legacy patterns that remain in-place from sheer
| inertia, with really very little relevancy today other than
| the inertia of the past.
| thefaux wrote:
| <snark> I'm glad you cleared this all up for us. </snark>
|
| Other people disagree with you and it's best to not assume
| they are idiots.
| xigoi wrote:
| Code uses much more punctuation than prose, and punctuation
| is hard to discern in a proportional font.
| jdougan wrote:
| Depends on which language you are writing in.
| Historically Smalltalk UIs use proportional fonts, and
| they work just fine.
| nanolith wrote:
| On my 4K monitor, I use 4-5 vertical splits and 2-3 horizontal
| splits. The 80 column rule makes each of these splits readable,
| and allows me to see the full context of a chunk of kernel code
| or firmware at once. It has nothing to do with "modern"
| hardware or "modern" IDEs. It has everything to do with fitting
| the most amount of relevant information that I can on the
| screen at once, in context, and properly formatted for reading.
|
| The 80 column rule may seem arbitrary, but it really helps
| analysis. I avoid open source code that ignores it, and I'll
| ding code that violates it during code review.
|
| If I had code marching off the screen, or rudely wrapped around
| so it violated spacing, I'd have to reduce the number of splits
| I used to see it, and that directly impacts my ability to see
| code in context. Modern IDEs don't reduce the need to see
| things in context. It's not a matter of organizing things in
| drop-down menus, smart tabs, font changes, or magic "refactor"
| commands. Verifying function contracts in most extant software
| -- which lacks modern tooling like model checking -- requires
| verifying these things by hand until these contracts can be
| codified by static assertions. This, in turn, requires
| examining function calls often 5-6 calls deep to ensure that
| the de facto specifications being built up don't miss
| assumptions made in code deep in the bowels of under-documented
| libraries. I'd be terribly upset if I had to try to do this in
| code that not only missed modern tooling but that was written
| by a developer who mistakenly believed that "80 columns is for
| geezers." I freely admit that, at 43, I probably count as a
| "geezer" to many young developers. But, that doesn't change the
| utility of this rule. Violations of contracts in software
| account for a large percentage of errors in software AND
| security vulnerabilities. Most of these violations are subtle
| and easy to miss unless you can see the call stack in context.
| No developer can keep hundreds of details from code that they
| did not write in their head with perfect clarity. It's
| incredibly nice to have uniform style and uniform maximum line
| lengths. By convention, 80 columns has shown itself to be the
| most stable of these limits.
|
| Even FAANG companies like Google follow this rule.
| kllrnohj wrote:
| > Even FAANG companies like Google follow this rule.
|
| Google also uses 100
| nanolith wrote:
| Most of their monorepo code I came across used 80 columns.
| But, I can't speak for all of it. Google has a LOT of code.
|
| Either way, if Google and other companies can do what they
| do in 80 columns, I think it's a fair constraint. What we
| get out of this constraint is the ability to put a lot of
| context on the screen.
| jcalvinowens wrote:
| IMHO if the 80-column limit bothers you in C, you're writing
| bad C. Quoting the kernel docs, it is "warning you when you're
| nesting your functions too deep. Heed that warning".
|
| I remember reading this for the first time as a teenager: "if
| you need more than 3 levels of indentation, you're screwed
| anyway, and should fix your program". Twenty years later, it
| seems like solid advice to me.
|
| https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
| ablob wrote:
| As soon as you collaborate with more people, 80 charactars
| becomes a valid target for line width. Eventually you'll have
| someone reading your code in a manner that is hardly pleasant
| with lengths of 200 characters or more: -
| Someone using a Braille display - Someone with a vision
| impairment (i.e. high scaling factor; common occurence during
| ageing) - A group of people that doesn't sit close to the
| display - Someone with a low-DPI (or small) display due
| to the normal workplace being unavailable
|
| While you could, of course, disregard all these scenarios, the
| sheer amount of people profiting from or requiring a character
| limit on lines is usually grounds for a restrictive policy
| regarding this topic. You might consider it silly, but as long
| as there is no reliable way to convert between these "styles of
| presentation" you will find that many people prefer to err on
| the safe side.
| lifthrasiir wrote:
| While I don't agree every single point (see below), one thing
| great about this document is that the author tried to really
| elaborate one's opinion. That makes a good point to start the
| discussion regardless of my own opinion. Thus I'll contribute
| back by giving my own judgement for every single item here:
|
| Absolute agreement * Always develop and compile
| with all warnings (and more) on * #include the definition
| of everything you use * Provide include guards for all
| headers to prevent double inclusion * Always comment
| `#endif`s of large conditional sections * Declare variables
| as late as possible * Be consistent in your variable names
| across functions * Minimize the scope of variables *
| Use `assert` everywhere your program would fail otherwise *
| Repeat `assert` calls; don't `&&` them together * C isn't
| object-oriented, and you shouldn't pretend it is
|
| Strong agreement with some obvious exceptions *
| Use `//` comments everywhere, never `/* ... */` * Comment
| non-standard-library `#include`s to say what symbols you use from
| them * No global or static variables if you can help it
| (you probably can) * Minimize what you expose; declare top-
| level names static where you can * Use `double` rather than
| `float`, unless you have a specific reason otherwise *
| Avoid non-pure or non-trivial function calls in expressions
| * Simple constant expressions can be easier to read than
| variables * Initialize strings as arrays, and use sizeof
| for byte size * Where possible, use `sizeof` on the
| variable; not the type * Document your struct invariants,
| and provide invariant checkers * Avoid `void *` because it
| harms type safety * If you have a `void *`, assign it to a
| typed variable as soon as possible * Only use pointers in
| structs for nullity, dynamic arrays or incomplete types *
| Avoid getters and setters
|
| Agreed but you need a few more words * Don't be
| afraid of short variable names [if the scope fits on a screen]
| * Explicitly compare values; don't rely on truthiness
| [unless values themselves are boolean] * Use parentheses
| for expressions where the operator precedence isn't obvious
| [but `&foo->bar` *is* obvious] * Separate functions and
| struct definitions with two lines [can use comments
| instead] * If a macro is specific to a function, `#define`
| it in the body [and `#undef` ASAP] * Only typedef structs;
| never basic types or pointers [or make them distinct
| enough, but ISO C stole a `_t` suffix]
|
| I do so or I see why but that's really a problem of C and its
| ecosystem instead * Use GCC's and Clang's `-M` to
| automatically generate object file dependencies * Avoid
| unified headers * Immutability saves lives: use `const`
| everywhere you can * Use `bool` from `stdbool.h` whenever
| you have a boolean value * Avoid unsigned types because the
| integer conversion rules are complicated * Prefer compound
| literals to superfluous variables * Never use array syntax
| for function arguments definitions * Don't use variable-
| length arrays * Use C11's anonymous structs and unions
| rather mutually-exclusive fields * Give structs TitleCase
| names, and typedef them * Never begin names with `_` or end
| them with `_t`: they're reserved for standards * Only use
| pointer arguments for nullity, arrays or modifications *
| Prefer to return a value rather than modifying pointers *
| Always use designated initializers in struct literals
|
| I do so but am not sure * Write to the most
| modern standard you can [we have no choice for many cases]
| * Program in American English [only applicable for native
| speakers]
|
| I see why but I think you are mislead * Don't
| write argument names in function prototypes if they just repeat
| the type [such case is very, very rare] * Use `+=
| 1` and `-= 1` over `++` and `--` [`++`/`--` should be
| read as succ/pred and should be exclusively used for pointers]
| * Don't use `switch`, and avoid complicated conditionals
| [switch is okay once you have enabled enough warnings] *
| Only upper-case a macro if will act differently than a function
| call [agreed in principle, but should define
| "differently" more broadly] * Always prefer array indexing
| over pointer arithmetic [and then you will be biten by
| index variable types, remember `ptrdiff_t`]
|
| That's really just a personal preference * We
| can't get tabs right, so use spaces everywhere [as long
| as mechanically enforcable, the choice itself is irrelevant]
| * Always put `const` on the right and read types right-to-left
| [too eyesore] * Use one line per variable definition; don't
| bunch same types together [will agree with some
| significant exceptions though] * Never change state within
| an expression (e.g. with assignments or `++`) [absolutely
| avoid functions, but `++` has its uses] * Always use
| brackets, even for single-statement block [rather a read-
| write trade-off; this may make some codes harder to read] *
| Never use or provide macros that wrap control structures like
| `for` [the example is very tame in comparison to actually
| problematic macros] * Don't typecast unless you have to
| (you probably don't) [while many typecasts can be easily
| removed, excess doesn't do actual harm] * Give enums
| `UPPERCASE_SNAKE` names, and lowercase their values [I
| would rather avoid enums for various reasons] * Use structs
| to name functions' optional arguments [maybe the author
| tried to say "avoid too many arguments" instead?] * If
| you're providing allocation and free functions only for a struct
| member, allocate memory for the whole struct
| [that complicates using struct as a value]
|
| Just no. * Never have more than 79 characters per
| line [100 or 120 do work equally well, you do need some
| limit though] * Define a constant for the size of every
| enum [would imply that all enum values are sequential,
| and that's not true!]
| rramadass wrote:
| Nice Summary of the Article ! Thank You.
| jstimpfle wrote:
| > * We can't get tabs right, so use spaces everywhere > [as
| long as mechanically enforcable, the choice itself is
| irrelevant]
|
| It's not mechanically enforceable (in practice), that's the
| point. Forbidding tabs altogether is the most practical and
| actionable path.
| lifthrasiir wrote:
| I'm not sure what you have in mind, but in my mind the
| mechanical enforcement really means something like clang-
| format [1] and that surely works.
|
| [1] https://clang.llvm.org/docs/ClangFormatStyleOptions.html#
| use...
| jstimpfle wrote:
| Have you actually tried? Personally I have evaluated and
| then abandonded clang-format (for reasons including but not
| limited to the interplay between macros and indentation),
| and most tool's support for tab-indentation + space-
| alignments is flaky to non-existent. I wouldn't want to
| constrain my setup to one that integrates clang-format just
| for a needlessly complicated requirement when I could just
| abandon tabs altogether.
| lifthrasiir wrote:
| If you want to keep space alignments that's honestly to
| be expected. Exclusively using tabs would work much
| better, partly for the reason you have said. On the
| requirement of clang-format itself though... Yeah, that
| is more like a problem of C and its ecosystem indeed.
| janice1999 wrote:
| > * No global or static variables if you can help it (you
| probably can)
|
| I can't imagine doing this on embedded systems.
| lifthrasiir wrote:
| That's what I meant by "some obvious exceptions" :-)
| jackiesshirt wrote:
| >Prefer compound literals to superfluous variables
|
| I used to agree with this but I have moved away from compound
| literals entirely except for global statics/const definitions.
|
| Having a variable and explicit: foo.x = whatever;
| foo.y = something_else;
|
| Leads to better debug experience imo, can set breakpoints and
| single step each assignment and have a name to put a watch on.
| flohofwoe wrote:
| Hmm, what's the point of single-stepping over a simple data
| assignment though? And when the initialization involves
| function calls, the debugger will step into those anyway.
|
| One advantage of initialization via compound literals is that
| you can make the target immutable, and you won't accidentially
| get any uninitialized junk in unlisted struct members, e.g.:
|
| const vec3 vec = { .x = 1.0, .y = 2.0 };
|
| ...vec.z will be default-initialized to zero, and vec doesn't
| need to be mutable.
| stef-13013 wrote:
| Very interesting.
|
| Just one (personal) stuff : Stick to 80 columns... Sorry, no ! :)
| sph wrote:
| > Always write to a standard, as in -std=c11. Don't write to a
| dialect, like gnu11. Try to make do without non-standard language
| extensions: you'll thank yourself later.
|
| Why not? Do people really care about porting their toy project to
| another compiler? If portability is a goal, avoid extensions, but
| not all projects need to be portable.
|
| I'm writing an Operating System, and I do not care it if compiles
| on Clang or MSVC. GCC has been around for decades, it is a safe
| bet.
| diath wrote:
| Personally I do, the Windows builds for my game use a Windows
| VM with MSVC, the Linux builds use GCC for certain
| optimizations, and for the dev builds I use Clang for
| sanitizers. Sure, you mention "a toy project" but he's talking
| about trying to avoid non-standard extensions in general, which
| is a fair point.
| 0xTJ wrote:
| Even when I'm writing a toy operating system, or other toy
| projects, I personally always try to use the standard options
| like -std=c11 (though don't have anything against people who
| use the dialect option).
|
| I'm happy to use compiler extensions, but I'll use __asm__
| instead of asm, and use __extension__ as needed. I've done some
| neat but truly upsetting things with compiler extensions in my
| hobby code, especially once I combine them with macros. I'm
| particularly "proud" of the mutex macros in a toy OS of mine,
| which wrap a statement inside for loops and switch statements
| for automatic release of the mutex, unless it's requested that
| it stay locked. There, I originally used compiler extensions to
| release the mutex on scope exit, but switched to non-compiler-
| extension code for the actual functions, and just using the
| extensions for checking that the code using the macros didn't
| break the "contracts" on what is allowed in those statements,
| and how they can be exited.
|
| It's the same reason that I'm always explicit about the size of
| integers, using stdint, even if I know that an int is 32-bit on
| a particular platform.
| astrobe_ wrote:
| The recommendation of using the most recent standard is very
| slightly inconsistent with this, because in rare cases your
| code could be reused in weird embedded targets that only have
| an unmaintained proprietary C compiler. That was already rare
| in 2014 I think. Still, the situation might arise, just like
| you can still can find AS/400 machines or Cobol in production.
| aap_ wrote:
| Funny, this is to a great extent opposite to how I write C.
| gavinhoward wrote:
| I agree with most, and most of the others I might quibble with,
| but accept.
|
| However, the item to not use unsigned types is vastly stupid!
| Signed types have _far_ more instances of UB, and in the face of
| 00UB [1], that is untenable.
|
| It is correct that mixing signed and unsigned is _really_ bad;
| don 't do this.
|
| Instead, use unsigned types for everything, including signed
| math. Yes, you can simulate two's complement with unsigned types,
| and you can do it without UB.
|
| On my part, all of my stuff uses unsigned, and when I get a
| signed type from the outside, the first thing I do is convert it
| safely, so I don't mix the two.
|
| This does mean you have to be careful in some ways. For example,
| when casting a "signed" type to a larger "signed" type, you need
| to explicitly check the sign bit and fill the extension with that
| bit.
|
| And yes, you need to use functions for math, which can be ugly.
| But you can make them static inline in a header so that they will
| be inlined.
|
| The result is that my code isn't subject to 00UB nearly as much.
|
| [1]: https://gavinhoward.com/2023/08/the-scourge-of-00ub/
| nwellnhof wrote:
| In the vast majority of cases, integer overflow or truncation
| when casting is a bug, regardless whether it is undefined,
| implementation-defined or well-defined behavior. Avoiding
| undefined behavior doesn't buy you anything.
|
| If you start to fuzz test with UBSan and -fsanitize=integer,
| you will realize that the choice of integer types doesn't
| matter much. Unsigned types have the benefit that overflowing
| the left end of the allowed range (zero) has a much better
| chance of being detected.
| gavinhoward wrote:
| > Avoiding undefined behavior doesn't buy you anything.
|
| This is absolutely false.
|
| Say you want to check if a mathematical operation will
| overflow. How do you do it with signed types?
|
| Answer: you can't. The compiler will delete any form of check
| you make because it's UB.
|
| (There might be really clever forms that avoid UB, but I
| haven't found them.)
|
| The problem with UB isn't UB, it's the compiler. If the
| compilers didn't take advantage of UB, then you would be
| right, but they do, so you're wrong.
|
| However, what if you did that same check with unsigned types?
| The compiler has to allow it.
|
| Even more importantly, you can implement crashes on overflow
| if you wish, to find those bugs, and I have done so. You can
| also implement it so the operation returns a bit saying
| whether it overflowed or not.
|
| You can't do that with signed types.
|
| > If you start to fuzz test with UBSan and
| -fsanitize=integer, you will realize that the choice of
| integer types doesn't matter much.
|
| I do this, and this is exactly why I think it matters. Every
| time they report UB is a chance for the compiler to
| maliciously destroy your hard work.
| lelanthran wrote:
| > In the vast majority of cases, integer overflow or
| truncation when casting is a bug, regardless whether it is
| undefined, implementation-defined or well-defined behavior.
| Avoiding undefined behavior doesn't buy you anything.
|
| With respect, this is nonsense. With UB, the compiler might
| remove the line of code entirely. With
| overflow/underflow/truncation, the results are well-defined
| and the compiler is _not allowed_ to simply remove the
| offending line.
| mcinglis wrote:
| Author here, 10 years later -- I agree. I'd remove that rule
| wholesale in an update of this guide. Unsigned integer types
| can and should be used, _especially_ for memory sizes.
|
| I would still advocate for large signed types over unsigned
| types for most _domain-level measurements_. Even if you think
| you "can't" have a negative balance or distance field, use a
| signed integer type so that underflows are more correct.
|
| Although validating bounds would be strictly better, in many
| large contexts you can't tie validation to the representation,
| such as across most isolation boundaries (IPC, network, ...).
| For example, you see signed integer types much more often in
| service APIs and IDLs, and I think that's usually the right
| call.
| gavinhoward wrote:
| I think with those changes, my disagreement would become a
| mere quibble.
|
| > I would still advocate for large signed types over unsigned
| types for most domain-level measurements. Even if you think
| you "can't" have a negative balance or distance field, use a
| signed integer type so that underflows are more correct.
|
| I agree with this, but I think I would personally still use
| unsigned types simulating two's complement that gives the
| correct underflow semantics. Yeah, I'm a hard egg.
| hgyjnbdet wrote:
| How many of these are applicable to other languages?
| xigoi wrote:
| Probably not many. Most of the rules are workarounds for C's
| design flaws.
| mcinglis wrote:
| I was surprised to see this on the HN front page, after so many
| years. Thanks for sharing it!
|
| Suffice to say: my opinions on this topic have shifted
| significantly. A decade+ more of programming-in-the-large, and I
| no longer pay much heed to written-in-prose style guides.
| Instead, I've found mechanistic "style" enforcement and close-to-
| live-feedback much more effective for maintaining code quality
| over time.
|
| A subtext is that I wrote this during a period of work - solo
| programmer, small company - on a green-field power system
| microcontroller project; MODBUS comms, CSV data wrangling. I'd
| opted for C primarily for the appeal of having a codebase I could
| keep in my head (dependencies included!). There was much in-the-
| field development, debugging and redeployments, so it was really
| valuable to have a thin stack, and an easy build process.
|
| So, other than one vendored third-party package, I had total
| control over that codebase's style. And so, I had the space to
| consider and evolve my C programming style, reflecting on what I
| considered was working best for that code.
|
| My personal C code style has since shifted significantly, as well
| - much more towards older, more-conventional styles.
|
| Still, opinionated, idiosyncratic documents like this - if
| nothing else - can serve as fun discussion prompts. I'm
| appreciating all the discussion here!
| bjourne wrote:
| Update the text! I would love to read the diff.
| mjevans wrote:
| Tabs vs Spaces
|
| Tabs are always correct, IF spaces are never used instead. One
| tab, for one level of indent. Adjust to preference.
|
| Alas, I don't think there's a standard way of specifying...
|
| // kate: space-indent off; indent-width 8; tab-width 8;
| mixedindent off; indent-mode tab;
|
| Similarly, // comments should be preferred, but /* comments */
| are acceptable at the top of large function blocks for large
| blobs of comments. Judicious / sparing use as the key idea to
| make it worth the exceptions if commenting out large blocks
| during tests or refactors.
| jdougan wrote:
| There is always .editorconfig [1] to setup indent if you have a
| directory of files. In places where it really matters (Python)
| I'll always comment with what I've used.
|
| [1] https://editorconfig.org/
| kloch wrote:
| > Never have more than 79 characters per line Never write lines
| longer than 79 characters.
|
| I'm sorry, I just cannot do this. I start to feel somewhat guity
| after 300 characters but 80 feels like an Atari 800.
| yoochan wrote:
| "We can't get tabs right, so use spaces everywhere"
|
| I'm more like: Always use tabs, never use space. Code doesn't
| need to be "aligned" it's not some ASCIIart masterpiece...
|
| One tab means one indentation level and if your taste is to have
| tabs of pi chars wide, nice! But it won't mess my code
| pquki4 wrote:
| Ah, seeing people posting coding styles, and other people
| debating them, I know my life is too short to join this
| conversation.
| kragen wrote:
| i was sort of hoping for something like
| https://nullprogram.com/blog/2023/10/08/, which shows a bunch of
| inventions that simplify your programming. some of them may be
| more trouble than they're worth, but they're at least novel and
| interesting
|
| by contrast, this document is largely motherhood and apple pie --
| and where it isn't ( _e.g._ , when it advocates titlecasing
| struct types or never typedeffing primitive types), i often think
| it's wrong. 'Write assertions to meaningfully crash your program
| before it does something stupid, ... to prevent a security
| vulnerability' is especially wrong; one of the major features of
| the standard assert() macro is that it's turned off in release
| builds!
|
| the named-arguments macro hack is an example of the kind of thing
| i was most hoping to find in here
|
| if you write something like this, don't dilute whatever value it
| may have with your opinions about tabs vs. spaces, line length,
| what natural language to write your comments and identifiers in,
| include guards, how many blank lines to put between functions,
| etc. these have been debated to death, and you're unlikely to
| have any brilliant insights about them that other people will be
| happy to have read
___________________________________________________________________
(page generated 2024-05-20 23:01 UTC)