These kinds of recommendations are good, but not exactly what I was looking for, probably because they are a bit too high-level. Let me give you an example of the types of things I think have made me an incrementally better programmer recently:
Using a scripting language (Jython or Matlab) in combination with a compiled language (Java) to get the best of both approaches. One-off, adhoc analyses or jobs can be quickly developed in the scripting language, and then either thrown away or migrated to the compiled language if they prove to be useful. The ability of a tool like Jython or Matlab to script Java objects means that you can reuse your highly polished object logic in the scripting language.
Enforcing a “no-cycles” rule in my list of object and package dependencies. A full build of my code compiles package-by-package, so that if package A is ordered before package B in the list, package A cannot refer to package B. This is a good straitjacket to put yourself in, but I actually take it a step further and require that there cannot be circular object dependencies within a package.
A “smart” fast compilation script that checks if a source file has been modified and then recompiles just that file. This is actually kind of fast and loose in the sense that it can introduce bugs if you’re not careful.
Extensive use of assertions. Assertions actually serve two purposes—the obvious purpose of producing a “fail-fast” effect so that the code breaks immediately and for a clearly specified reason when the assertion fails. But they also serve for self-documentation. For example, say you are using a method that takes two arrays, one of size NxM and the other of size NxP. This introduces the potential for bugs if the array sizes don’t line up and also confusion if the caller can’t figure out whether s/he needs to pass an array or its transpose. Putting in the appropriate assertion both prevents confusing error messages but also indicates to the reader what the correct array sizes are.
Figuring out how to rely on the compiler more to catch potential errors at compile time. By writing code in a certain way, you can be more confident that IF there is an error, the compiler will catch it. One principle here is to use methods with mixed signatures—it’s better to have mymeth(String, int, boolean) than mymeth(String, String, String) because in the latter case the compiler won’t be able to catch it when you pass the arguments in the wrong order. Using enums is also a good idea.
Use of AutoHotKey to remap common commands to keyboard shortcuts that are easier on the fingers. For example, I remap cut and paste operations to the function keys, because I don’t like stretching my left pinky finger all the way out there to hit the control key.
Some things I need to get better at:
I need to know more about Unix. I have been procrastinating on taking the plunge and switching over to Linux for personal use.
Overall I need to have a more efficient computing environment—not just the programming language but the full ecosystem of tools: editor, shell, scripting language, keyboard, etc. (Aha, I just realized that I want my shell to produce sounds or music to supplement the text output so I can actually hear it if a program is behaving improperly).
I need to get better at building modular parameterized components and reusing them to build more complex applications. The Unix pipe-output-to-input is the model here, but it doesn’t do all the things I need it do. I can imagine a visual “wiring diagram” tool here (maybe similar to SpaceChem) that allows the user to hook up programs to each other, using basically the same piping idea, but in a more flexible way.
I should be able to make better estimates both for how long various process will take and how long they should take if my code were perfectly efficient (i.e. how close am I getting to the actual physical limitations of the hardware).
I should get better at using code analysis tools to detect errors. One thing that constantly annoys me is variable shadowing, and that should be possible to detect automatically ( I know some IDEs do this. I have mixed feelings about IDEs).
I am a big fan of IDEs, because they can remove a lot of trivial (or not so trivial) inconveniences.
I point a mouse cursor at any variable / function / class, and a documentation is displayed; if I press Ctrl and click, the definition is opened. What is the alternative: Remembering everything?
I often rename my variables / functions / classes, because I believe that the name should reflect the meaning as closely as possible, but sometimes when I finish writing something, I realize the meaning is slightly different than I originally thought. IDE replaces all instances of the given thing, but does not touch other things with the same name (e.g. a local variable in a different function, or a method of a different class). What is the alternative: Always thinking hard about the name? Leaving the old name even when it feels wrong? Check carefully whether only the correct words are being replaced?
Generally, when I work with a good IDE, I am not just editing a set of text files, but I feel like I am working with the generated classes and functions directly. Because it is so responsive, it feels more real. When I work without the IDE (or with a bad IDE), it feels very clumsy. For example, I can type “my_obj.my_func()” on the keyboard, but I don’t get an immediate response whether an object “my_obj” exists, whether it has a function “my_func”, whether the function has zero parameters. The IDE gives me this feedback all the time, it feels so natural, and I become tense without it. Sure, I can get the feedback later, from the compiler. But that’s like a difference between walking with open eyes, and walking with closed eyes and shortly looking around once in a minute. I can still successfully navigate to my goal, it just takes a heavy tax on my attention.
My favourite IDE is Eclipse, for Java. In settings it has a long list of things some people consider bad programming style, and you can select which of them will be underlined in the source code as warnings. Just reading the whole list is interesting. Once I tried to turn on all of them and then improve my program to fully pass the test. (Then I turned off one setting. It required localizing all the strings in code; that would be an overkill.) I learned some interesting things from that. But this could be achieved also by using an external tool, such as FindBugs or PMD.
require that there cannot be circular object dependencies within a package.
How do you avoid circular dependencies between e.g. “a node is a branch or a leaf” and “a branch contains two nodes”?
I agree with the other things (with the sidenote that some IDEs give you smart compilation and keyboard shortcuts automatically). The idea of an acoustic feedback sounds interesting!
If you want to develop your *nix skills, switching to Linux for personal use will not be as much help as you think. The most sure-fire way of picking up Linux chops is having to work with a core installation you can only access through the command line.
These kinds of recommendations are good, but not exactly what I was looking for, probably because they are a bit too high-level. Let me give you an example of the types of things I think have made me an incrementally better programmer recently:
Using a scripting language (Jython or Matlab) in combination with a compiled language (Java) to get the best of both approaches. One-off, adhoc analyses or jobs can be quickly developed in the scripting language, and then either thrown away or migrated to the compiled language if they prove to be useful. The ability of a tool like Jython or Matlab to script Java objects means that you can reuse your highly polished object logic in the scripting language.
Enforcing a “no-cycles” rule in my list of object and package dependencies. A full build of my code compiles package-by-package, so that if package A is ordered before package B in the list, package A cannot refer to package B. This is a good straitjacket to put yourself in, but I actually take it a step further and require that there cannot be circular object dependencies within a package.
A “smart” fast compilation script that checks if a source file has been modified and then recompiles just that file. This is actually kind of fast and loose in the sense that it can introduce bugs if you’re not careful.
Extensive use of assertions. Assertions actually serve two purposes—the obvious purpose of producing a “fail-fast” effect so that the code breaks immediately and for a clearly specified reason when the assertion fails. But they also serve for self-documentation. For example, say you are using a method that takes two arrays, one of size NxM and the other of size NxP. This introduces the potential for bugs if the array sizes don’t line up and also confusion if the caller can’t figure out whether s/he needs to pass an array or its transpose. Putting in the appropriate assertion both prevents confusing error messages but also indicates to the reader what the correct array sizes are.
Figuring out how to rely on the compiler more to catch potential errors at compile time. By writing code in a certain way, you can be more confident that IF there is an error, the compiler will catch it. One principle here is to use methods with mixed signatures—it’s better to have mymeth(String, int, boolean) than mymeth(String, String, String) because in the latter case the compiler won’t be able to catch it when you pass the arguments in the wrong order. Using enums is also a good idea.
Use of AutoHotKey to remap common commands to keyboard shortcuts that are easier on the fingers. For example, I remap cut and paste operations to the function keys, because I don’t like stretching my left pinky finger all the way out there to hit the control key.
Some things I need to get better at:
I need to know more about Unix. I have been procrastinating on taking the plunge and switching over to Linux for personal use.
Overall I need to have a more efficient computing environment—not just the programming language but the full ecosystem of tools: editor, shell, scripting language, keyboard, etc. (Aha, I just realized that I want my shell to produce sounds or music to supplement the text output so I can actually hear it if a program is behaving improperly).
I need to get better at building modular parameterized components and reusing them to build more complex applications. The Unix pipe-output-to-input is the model here, but it doesn’t do all the things I need it do. I can imagine a visual “wiring diagram” tool here (maybe similar to SpaceChem) that allows the user to hook up programs to each other, using basically the same piping idea, but in a more flexible way.
I should be able to make better estimates both for how long various process will take and how long they should take if my code were perfectly efficient (i.e. how close am I getting to the actual physical limitations of the hardware).
I should get better at using code analysis tools to detect errors. One thing that constantly annoys me is variable shadowing, and that should be possible to detect automatically ( I know some IDEs do this. I have mixed feelings about IDEs).
I am a big fan of IDEs, because they can remove a lot of trivial (or not so trivial) inconveniences.
I point a mouse cursor at any variable / function / class, and a documentation is displayed; if I press Ctrl and click, the definition is opened. What is the alternative: Remembering everything?
I often rename my variables / functions / classes, because I believe that the name should reflect the meaning as closely as possible, but sometimes when I finish writing something, I realize the meaning is slightly different than I originally thought. IDE replaces all instances of the given thing, but does not touch other things with the same name (e.g. a local variable in a different function, or a method of a different class). What is the alternative: Always thinking hard about the name? Leaving the old name even when it feels wrong? Check carefully whether only the correct words are being replaced?
Generally, when I work with a good IDE, I am not just editing a set of text files, but I feel like I am working with the generated classes and functions directly. Because it is so responsive, it feels more real. When I work without the IDE (or with a bad IDE), it feels very clumsy. For example, I can type “my_obj.my_func()” on the keyboard, but I don’t get an immediate response whether an object “my_obj” exists, whether it has a function “my_func”, whether the function has zero parameters. The IDE gives me this feedback all the time, it feels so natural, and I become tense without it. Sure, I can get the feedback later, from the compiler. But that’s like a difference between walking with open eyes, and walking with closed eyes and shortly looking around once in a minute. I can still successfully navigate to my goal, it just takes a heavy tax on my attention.
My favourite IDE is Eclipse, for Java. In settings it has a long list of things some people consider bad programming style, and you can select which of them will be underlined in the source code as warnings. Just reading the whole list is interesting. Once I tried to turn on all of them and then improve my program to fully pass the test. (Then I turned off one setting. It required localizing all the strings in code; that would be an overkill.) I learned some interesting things from that. But this could be achieved also by using an external tool, such as FindBugs or PMD.
How do you avoid circular dependencies between e.g. “a node is a branch or a leaf” and “a branch contains two nodes”?
I agree with the other things (with the sidenote that some IDEs give you smart compilation and keyboard shortcuts automatically). The idea of an acoustic feedback sounds interesting!
If you want to develop your *nix skills, switching to Linux for personal use will not be as much help as you think. The most sure-fire way of picking up Linux chops is having to work with a core installation you can only access through the command line.