11 ms·
Clean your codebase with basic information theory
- nighthawk454 3y agoBit of roundabout way to say ‘DRY’. Information isn’t a universal context-free quantity, it depends on the models involved. In this case the target is removing repeated words/code symbols and being concise.
- userbinator 3y agoI've also thought of the idea of using some LZ-based compression on source code files to determine which ones have the most redundancy (the ones that have the best ratios) and could be simplified by refactoring, which is not too different from the entropy-based approach described here. It's worth noting that this also identifies languages that trend towards boilerplate and egregious verbosity --- for example, I've noticed that the average C# or Java codebase will compress much better than C, while (much) denser stuff like APL-family languages don't compress as much.
- aappleby 3y agoThe article is somewhat silly, but there's a kernel of good advice here - To estimate the "complexity" of a codebase: 1. Remove all comments 2. Replace all spans of whitespace with a single space 3. Concatenate all source together into a single file 4. Compress the resulting text file using gzip -9 (or your favorite compression engine) The size of the resulting file is a good proxy for overall complexity. It's not heavily affected by naming conventions, and a refactoring that reduces the number is probably good for overall complexity. It's not a perfect metric as it doesn't include any notion of cyclomatic complexity, but it's a good start and useful to track over time.
- smburdick 3y agoThis is what a minifier does, and those go even further to rename variables. Another thing that should be pruned away entirely are data files, including all constant strings within the code, since humans should avoid those when focusing on algorithms At that point you pretty much have a highly compressed version of what you'd find in CLRS or any other algorithmic text.
- CapsAdmin 3y agoSometimes I've scanned code bases of my own for all user definable variable names and just levenshtein distanced them. It's kind of useful, but the hurdle for me at least is that I need to run something in a terminal to get the results. Maybe I'd use it more if it was a plugin in my ide of choice. Something else you could maybe do is to simplify the code and compare sequences of statements and expressions to each other. ie the 2 statements "foo = bar; foo += 20" is identical to "zoo = war; zoo += 20"
- sltkr 3y agoI think the value of metrics like this is limited, since code base size only very roughly corresponds to implementation complexity. Here are some examples where you would increase the compressed code size while not making the project more complex: 1. Adding unit tests to code that was previously untested. Unit tests add little complexity because they don't introduce new interfaces. 2. Splitting a God class up into multiple independent classes. Usually this improves readability thanks to separation of concerns, but it often increases raw code size because each new class adds some boiler plate. etc.
- kqr 3y ago> I think the value of metrics like this is limited, since code base size only very roughly corresponds to implementation complexity. This sounds a lot like the "your model is wrong because nuance X" argument. I want to remind you that all models are wrong, but some of them are useful anyway. In particular, I have found the size of source code to be a highly useful predictor of complexity. It has helped me predict where bugs are, where changes are made, where developers point out areas of large technical debt, and many other variables associated with complexity. The test of a model is not whether it accounts for all theoretical nuances, but rather whether it's empirically useful – and critically, has higher return-on-investment than alternative models. What model do you suggest for implementation complexity that you have verified to be better than code size? Genuinely interested! (Additionally, I have also successfully used the compressed size of input data to predict the resource requirements of processing that data, without actually having to process it first. This is useful because the compressed size can be approximated on-line rather cheaply.)
- whynotmaybe 3y agoIsn't it another way of expressing the Halstead complexity measure ? https://en.m.wikipedia.org/wiki/Halstead_complexity_measures https://en.m.wikipedia.org/wiki/Halstead_complexity_measures
- photonthug 3y ago> This is similar to the identification of measurable properties of matter (like the volume, mass, and pressure of a gas) and the relationships between them (analogous to the gas equation). Thus his metrics are actually not just complexity metrics. I thought I was familiar with halstead but this perspective was new to me. Interesting
- photonthug 3y agoFor related topics filed under complexity/compressibility see also Kolmogorov complexity, algorithmic information theory, and the hutter prize
- lmm 3y agoTo my mind the conclusion is backwards. A file with a high compressed size might be doing something useful; a file with a low compressed size but a high uncompressed size is a file that's full of repetitive junk, and those are the files that should be a target for refactoring.
- TeMPOraL 3y agoExactly. As ways to differentiate between "essential complexity" and "accidental complexity" go, the idea of looking at what compresses well sounds quite good - but it's the accidental complexity that will compress the best, and essential the worst. And latter is not the problem, the former is.
- deleted 3y ago[deleted]
- userbinator 3y agoIn other words, the compression ratio is what's important.
- nyrikki 3y agoThat is the entire basically the entire concept of complexity theory. The reals are compressed into the computable reals. The real numbers are un-computable 'almost everywhere' Semi-decidabity is just recursively enumerable that gets you to finite time with unlimited resources. NP-hard is brute forcible in exponential time, with most likely no approximate polynomial reductions. P has exact polynomial time reductions... Most code is made of IF and FOR loops, because that produces primitive recursive functions, which are most of the intuitive computable functions that always halt. The problem becomes with complex systems where you need to balance coupling with cohesion along with free variables, WHILE and GOTO. Note that the above compression was lossy. If you consider that information loss as setting constraints through heuristics, (educated guesses) those constraints may or may not work for a particular use case. The problem with how we often try to set coding style is that we want simple universal rules. Unless you have a system that fits in with those ideals all the time that is problematic. Gödel demonstrated that isn't possible for complex systems. Either our rules will be inconsistent or incomplete. This is why I think selling conventions as ideals, that need to yield to less preferred cohesion models when appropriate is the real solution. Unfortunately that requires a lot more thought and vigilance. Functional cohesion with loose coupling is what we shoot for when it is appropriate, but not as a hard fast rule.
- zokier 3y agoI feel the approach could have some merit, but the article unfortunately stops short of demonstrating it. Also the problem of analytically finding similar code sub-trees seems highly non-trivial. As baseline you want to parse the code, but really you'd also want to somehow "normalize" identifiers, as usually it is the common structure rather than specific identifiers that matter.
- zem 3y agotake a look at https://pmd.github.io/#about https://pmd.github.io/#about
- shoo 3y ago> group related concepts together > The hardest part of this process is deciding what “related concepts” mean. The article talks about "readability", but arguably the unnamed hard problem it is dancing around is how to structure an application or system by decomposing it into modules. I'd argue the baseline reasonable approach to structuring applications or systems is the one given in Parnas' 1972 paper "On the Criteria To Be Used in Decomposing Systems into Modules": > We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others. http://sunnyday.mit.edu/16.355/parnas-criteria.html http://sunnyday.mit.edu/16.355/parnas-criteria.html Parnas' criterion embeds the understanding that code and systems are not static but need to evolve over time as requirements change or decisions are made, and that different decompositions can be inferior or superior to accommodating that change. "Don't repeat yourself" refactoring rules of thumb can give poor results if blindly applied. Suppose two sections of application logic just so happen to look similar at this moment in time and get refactored to "remove the duplication" coupling them together, when the two sections of code are subject to different constraints and reasons for change, and will need to evolve separately.
- TeMPOraL 3y agoPeople forget that readability isn't a function of specific program - there is no one optimal readability. On the contrary, it's a function of the program and the goals of the reader. So after fixing and DRYing all the generally bad/inefficient decision, what is readable code becomes solely the issue of why you're reading it - trying to debug or add an entirely new feature will have opposite readability criteria to extending some high-level feature. Even in the best case, readability just becomes a Pareto frontier[0], given by expressive limits of the dominant programming paradigm - same single plaintext source code for all. There's only so much complexity, so much cross-cutting concerns, we can cram into the same piece of plaintext, until something gives, until the same code is beatiful to you one week, and incomprehensible the next week, with the only thing that changed is the type of work you're doing on it. So, beyond evolving over time, I'd also consider the orthogonal aspect of different decompositions being good for different purposes, and that you can't have it all and work on the same, single, high-level plaintext code. EDIT: And I believe the solution to this, the step forward beyond the Pareto frontier, is what 'valty described here: https://news.ycombinator.com/item?id=39426895 https://news.ycombinator.com/item?id=39426895 - not coding directly in the same plaintext, but treating the single-source-of-truth code as a database, which you query and update through views/lenses that best fit whatever work you're doing at the moment. -- [0] - https://en.wikipedia.org/wiki/Pareto_front https://en.wikipedia.org/wiki/Pareto_front
- valty 3y ago> In my experience, the key to maintaining readability is developing a healthy respect for locality I think this pursuit of "locality" is what actually causes more complexity. And I think its mainly around our obsession with representing our code as text files in folder hierarchies. > coarsely structure codebases around CPU timelines and dataflow This is why I would prefer code to be in a database, instead of files and folders, so that structure doesn't matter, and the tree view UI can be organized based on runtime code paths, and data flow - via value tracing. > don’t pollute your namespace – use blocks to restrict variables/functions to the smallest possible scope Everyone likes to be all modular and develop in tiny little pieces that they assemble together. Relying on modularization means that when stuff changes upstream in the call stack, we just hack around these changes adding some conditionals to handle these changes instead of resorting to larger refactors. People like this because things can keep moving instead of everything breaking. Instead, what we need to do is make it easier to trace all the data dependencies in our programs so that when we make a change to anything, we can instantly see what depends on it and needs updating. I have actually started to think that, against conventional wisdom, everything should be a global variable. All the issues with global variables can be solved with better tracing tooling. Instead we end up with all these little mini-databases spread all over our code, when what we should have is one central one from which we can clearly see all the data dependencies. > group related concepts together Instead, we should query a database of code as needed...just like we do with our normalized data.
- hnben 3y agoYour ideas sound intriguing. Are they original, or can I read up on them somewhere?
- elbear 3y agoAs far as I'm aware, the Unison Language implements some of his ideas: https://www.unison-lang.org https://www.unison-lang.org
- hcs 3y agoI've heard the name Intentional Programming applied to this or a similar concept https://en.wikipedia.org/wiki/Intentional_programming https://en.wikipedia.org/wiki/Intentional_programming > Tight integration of the environment with the storage format brings some of the nicer features of database normalization to source code. Redundancy is eliminated by giving each definition a unique identity, and storing the name of variables and operators in exactly one place.
- crq-yml 3y agoMany of the issues that come up with applied information theory to practical code are of the form "oh, but it won't be fast if we do it that way". The end of the article links Alan Kay discussing STEPS and how it solves many computing. fundamentals needed for a desktop in miniscule amounts of code. One of the comments to that video, made five years ago, dismisses it as unrealistic ivory tower nonsense that can't run fast enough. (Notwithstanding, the presentation was given on a running system demonstrating the proof of concept) But there is a similar sentiment to Kay's from the bottom-up viewpoint. The Forth community, who have made livings on implementing this kind of succinct design in commercial settings, tend to point to hardware manufacturers themselves as the primary difficulty. Their business is to sell you more hardware than you need, and that leads them towards doing nothing to help with the software crisis, but rather, to encourage processing and I/O to be complex things to reason about, with complex protocols and mystery-meat drivers. If you have to use USB, Bluetooth, TCP/IP...you're stuck. Nobody wants to deal with those hot potatoes. You can't address it properly by running up the abstraction stack and doing "everything in the browser". That's playing nicely with the standards instead of attacking them. When software companies play along and say "well, it's the standard so we have to use it," their problem gets deeper. Some room could be conceded to say that some of that complexity is essential, but one of the ways in which we describe progress in science and technology is to find solutions that are lighter and simpler to understand, e.g. instead of astronomical tables describing "Earth at the center of the universe" epicycles, smaller equations describing orbits around the Sun.
- AriedK 3y agoI like how https://hemingwayapp.com/ https://hemingwayapp.com/ approached this for text. It highlights complex sentences, unnecessary adverbs and voicing issues.
- wiz21c 3y agoAt least the article proposes an easy to grasp application of entropy (which is not an easy to grasp concept)
- Tade0 3y agoI can half-agree with this, but I would measure it differently. It's not the words themselves that are surprising, but the range of language features used. I have a friend who produces particularly readable (TypeScript) code - at least in my opinion. For a long time I couldn't figure out what was so special about it, then it dawned on me that he simply doesn't use the more fancy and recent features of the language - by recent I mean stuff that came out in the last four years or so. I wouldn't be so radical in my approach, but I believe feature usage should obey a power law, with more advanced parts occurring less frequently. I'm sure there's a balance to be struck between terseness and using as minimal a subset of the language as possible. Also it doesn't hurt to have whitespace here and there. We use paragraphs in written speech for a reason.
- ozim 3y agoCyclomatic complexity is much better for code analysis than pure entropy analyses. Using linters and automated code formatting deals with entropy on level of words/characters rather well. So it is not useful metric for current state of art development.
- gabigrin 3y agoGreat read, as always! The combination of the Elm reference and "coarsely structure codebases around CPU timelines and dataflow" statement reminded me of this great talk by Evan Czaplicki (Elm creator): https://www.youtube.com/watch?v=XpDsk374LDE https://www.youtube.com/watch?v=XpDsk374LDE