Obfustication is generally done as part of building and packaging. Most often it works on the already compiled files, and the programmers are never aware of it.
The most popular Java development environment (Eclipse) has a really cool feature called \"Refactor\" which is like search and replace on steroids. Unless you are using some types of soft data references it lets you rename any class or function and scans and updates only the relevant parts of the code. It has saved me many times from stupid variables names that I used early in the development process.
[Longer explanation if you care...
Compiled languages (such as C or C++) go through two phase. First each files is compiled in to an object file. This contains a text based table of all the references to things outside that file. E.g. variables, methods, classes, etc. In the second phase, all those object files are linked together into one big, happy, executable. At that point these textual references are replaces with the actual code locations as laid down by the linker. Since \"go to byte 1234\" takes less space and executes faster than \"go look up where \'foobar\' ended up, and go there\", it is more efficient.
Interpreted languages, such as Java, stop at the compiled phase. A class file retains the textual references. For an outgoing reference, it has the name of the target class, and the name of the entity in that class. For itself, it keps an index of where in the bytecode each name it defines lives. When the program is run, the linking stuff is done as it runs. It is slower, but more flexible.
[[In fact, most modern versions of Java do a \"just-in-time compile\", which kind of does the linking as it goes. So it ends up being much of a muchness. But my digression digreses.]]
So, to circle back, what an obfusticator does is look at all these tables. For the index in each class, it comes up with either random or serialzed names. So the first class might get renamed A, the second B, etc. The first method in the first class might become A.a1(), the second A.a2(), etc. It then goes and look at all the other class files for references to this class file. When it finds them, it updates the tables in the other classes with the updated names.
So, via this method, all the class, method, and variable names in your code (or a section of it) can get renamed to confusing things without impacting the integrity of the code. So it can be done downstream from development and test.
The nifty refactoring I mentioned? It actually operates according to the same mechanism. It can work out, definitively, who looks at the function/class/variable you want to rename and update those references. Although, in that case, you are making the code easier to read, not harder!]
Jo