English · Português
Which parts of a program are actually load-bearing?
CodeDNA answers that with an experiment borrowed from genetics: knock out one gene, watch what the organism does, put it back, repeat. Here the organism is a Java program and the genes are its methods.
source code -> run -> remove one method -> run again -> compare -> essentiality
The whole analyser is one file, ~120 lines, zero dependencies.
Coverage tells you which lines ran. Complexity metrics tell you which lines are hard to read. Neither tells you what you actually want to know when you open an unfamiliar codebase:
If this method disappeared tomorrow, would anyone notice?
That question is normally answered by deleting things and seeing what breaks — which nobody does, because it is tedious and destructive. CodeDNA does exactly that, automatically, on a copy.
Geneticists identify what a gene is for by disabling it and observing the resulting organism. A knockout that kills the organism marks an essential gene. A knockout nothing notices marks a passenger.
CodeDNA applies the same protocol to code. It empties one method body at a time — keeping the signature so everything still compiles — recompiles the program, runs it, and measures how much of the original behaviour survived.
Essentiality = the fraction of the program's observable behaviour that disappears when the method is removed.
No annotations, no test framework, no config file. It reads a .java file and reports.
- Run the untouched program and record its output. This is the healthy phenotype.
- Find every method by brace depth, and for each one produce a variant of the source where that
body is replaced by
return 0;/return null;/ nothing, depending on the return type. - Compile and run each variant in an isolated classloader, capturing its output.
- Compare against the healthy output, line by line. Lines that no longer appear are behaviour lost. A variant that crashes or fails to compile scores 100% — the organism is not viable.
- Draw the result as a bar chart.
That is the entire program. javax.tools.JavaCompiler — the compiler that already ships inside every
JDK — does the compiling, so there is no build system and nothing to install.
public class Demo {
static int[] parse(String order) { ... }
static boolean valid(int[] item) { ... }
static int total(int[] item) { ... }
static String format(int[] item, int total) { ... }
static void log(String message) { ... }
static void debug(String message) { ... }
public static void main(String[] args) { ... }
}javac -d build src/CodeDNA.java
java -cp build CodeDNA examples/Demo.javaCODE DNA
parse() ██████████ 100%
valid() ███████ 67%
total() ███████ 67%
format() ██████ 56%
log() ██ 22%
debug() █ 11%
Read from the top: without parse() the program does not survive at all. valid() and total()
carry the same weight — remove either one and two thirds of the output changes. format() only
affects how results are printed, not whether they are right. log() and debug() are passengers:
the program computes exactly the same answers without them.
Nobody wrote a test, an assertion or an annotation to produce this. It came out of the source file alone.
notebook/CodeDNA.ipynb runs the same analysis and plots it.
A JDK 17 or newer — that is all. The notebook additionally uses matplotlib for the chart.
These are honest boundaries of the MVP, not a roadmap in disguise:
- One file, one entry point. The subject must be a single
.javafile with amainmethod. - Behaviour means stdout. Return values, files and side effects are invisible to the oracle. A method that only mutates state and prints nothing scores 0%.
- A chatty method looks important. Essentiality is measured against printed output, so a logger that prints half the lines is scored as half the behaviour. Judging against assertions instead of output would fix this.
- Coverage bounds the verdict. A method that this particular run never reaches scores 0%, whether or not it matters elsewhere.
- The parser is a pattern, not a Java grammar. It finds a method only when the signature starts
with a modifier (
static,public...). A package-private method (void run()), a generic method (static <T> T id(T x)) or a return type with a space in it (Map<String, Integer>) is not seen. - Mutants run in-process. A mutant that runs longer than 10 seconds (
-Dcodedna.timeout=Nto change it) is scored as dead, but its thread is abandoned rather than killed, andSystem.exitstill ends the whole analysis.
- Use a test suite as the oracle instead of stdout — essentiality becomes "percentage of tests that fail", which removes the chatty-logger bias.
- Double knockouts. Genetics calls it epistasis: does
Aonly matter whenBis missing? Pairwise knockouts would expose redundant code paths and silent fallbacks. - Other languages. Nothing here is Java-specific except the compiler call. Any language with a "compile" and a "run" step fits the same protocol.
- DNA as an artifact. Emit the profile as JSON and diff it across commits, to see essentiality shift as a codebase evolves.
MIT
