1. 18
  1. 11

    i was against commenting for many years - i felt like it just resulted in exploding the LOC without much benefit

    even now i still keep my commenting to a minimum - but commenting serves one critical purpose - to answer the question “Why?”

    Why was this idiom chosen instead of another? Why did you do this, or why didnt you do something else? Why is this seemingly wrong code being used. If you dont answer these questions, its possible someone else, or even yourself will go in a change some that was coded for a good reason. So its important to document those reasons, either in code or perhaps in Readme alongside the code.

    so anytime i code something that might be questionable i comment it.

    1. 2

      I feel commenting is a bit like managing dept, since it can be de-synchronized from the code and be of false value (you think it has but it does the opposite).

      Nonetheless, when reading code (even mine from several months ago), I’m quickly reminded how a small comment can go a long way to simplify a set of functions.

      I usually tend to prefer “high level” comments, explaining the whole idea than the implementation, but I do recognize that sometime, explaining why a specific one has been used is very useful.

    2. 4

      I feel like this article supports the more pie in the sky thesis “good code documents itself, languages today don’t let you write good code” just as well:

      For example

      /**
       * Returns the temperature.
       *
       * This is for testing purpose only and should
       * never be called from a real program.
       */
      int get_temperature(void) {
          return temperature;
      }
      

      Is clearly better written as

      #[cfg(test)]
      int get_temperature(void) {
          return temperature;
      }
      

      where #[cfg(test)] prevents it from even being called in a real program.

      Or the more extreme example

      /**
       * Returns the temperature in tenth degrees Celsius
       * in range [0..1000], or -1 in case of an error.
       *
       * The temperature itself is set in the periodically
       * executed read_temperature() function.
       *
       * Make sure to call init_adc() before calling this
       * function here, or you will get undefined data.
       */
      int get_temperature(void) {
          return temperature;
      }
      

      If the information in the comment is important in your application, you can imagine a language which makes it all first class something like this (quibbling over the details of course possible, for instance I’d like to record that C/10 part in the type somehow):

      global 
          temperature_in_c_over_10
          mutable_in init_adc, periodic_read_temperature,
          type Option Range (0, 1000),
          = None;
      

      No (current, or reasonably imaginable) language lets you encode all comments today, but maybe this should be a hint about where we should be heading. At the very least I think “Option Range (0, 1000)” is clearly better than “int” with a comment “[0, 1000], or -1 for an error”.

      1. 7

        You can make ever more expressive ways to say “what” and “how” outside of comments (indeed, that’s the history of programming language progress), but you will never be able to eliminate the need for “why”. Randomly chosen Linux kernel example:

        /*
         * It's common to FADV_DONTNEED right after
         * the read or write that instantiates the
         * pages, in which case there will be some
         * sitting on the local LRU cache. Try to
         * avoid the expensive remote drain and the
         * second cache tree walk below by flushing
         * them out right away.
         */
        lru_add_drain();
        
        1. 1

          I completely agree that that sort of information is necessarily a comment, and that it should be included as such. Also, that’s a great example of a “why” comment. I don’t really think that’s what the article was arguing for though.

          The article was arguing for including implementation details in the comments, and his arguments largely focused on “implementation details” that were really defining the intended interface in more detail. My response is really only aimed towards that category of comments.

        2. 4

          Trying to turn that last example into something you could actually do in a near future version of Rust

          #[derive(Copy)]
          struct Adc;
          
          pub fn init_adc() -> Adc { ...; Adc }
          
          pub mod temp_in_c_over_10 {
              static value: AtomicOption<Int<0, 1000>> = None;
          
              pub fn periodic_read(_token: Adc) {
                  value.set(...)
              }
          
              pub fn get(_token: Adc) -> Option<Int<0, 1000>> {
                   value.get()
              } 
          }
          

          Int<0, 1000> is a hypothetical type that contains an integer in [0, 1000] - with const generics this might be possible in a near future version of rust. That’s the only portion of this not possible in Rust today.

          Adc here is a 0 sized structure, and the copy on it means that you can create copies of it just by saying let new_version = old_version, or some_fn(old_version), or so on. This is a pattern sometimes used in real rust today to guarantee that you called whatever function gives you a copy of that token.

          Not sure this is actually better than the commented version, if it is I think it’s nearing the break even point in Rust. Whether you think that says more about Rust or the idea of encoding every comment possible in the language itself is a different question.

          1. 1

            You could make an argument that this is about mitigating the fact that using shared mutable global state can conceal information. init_adc is presumably mutating global state, and this is affecting the result of get_temperature. The fact that the shared state isn’t in the original type signature means that it has to be described in a comment instead.

            A example similar to your suggestion would be getting random numbers in Haskell. Rather than seeding a global RNG, then accessing that global RNG whenever you want a random number, it’s more explicit:

            random :: RandomGen g => g -> (a, g)
            

            The type signature tells you that you have to provide a random number generator, and that you will get back both a random number and a ‘modified’ random number generator ready to produce the next random number.

        3. 3

          Forth’s shadow blocks are a great solution to the comment problem. You’re looking at 1024 bytes of code and if you need it, 1024 bytes worth of commentary on that block. Maybe the problem is with today’s editors.

          1. 1

            I find one of the difficulties of documenting code is that there are two relevant parties; those who wrote the code, and those who will read the code.

            Those who wrote the code are best placed to explain why it does what it does and whether there are any gotchas that you need to be aware of. However, the person reading or trying to use the code is more keenly aware of which details aren’t obvious.

            All too often the writer leaves out important details, or gets hung up on whatever was on their mind when they wrote the code[1]. Meanwhile the reader gets frustrated, but often can’t fix the documentation themselves and may feel their failure to understand is their fault, not the fault of poor documentation, so they’re less likely to file bug reports or complain vocally.

            I feel like there’s definitely room for improvement in this process, so that poor documentation can get flagged and fixed with minimum effort on both sides.

            [1] I recently read some old C++ code which I think was written not long after the writer learnt C++. It was full of comments explaining how C++ worked.