When I want to refactor code, I first make sure we're working in a programming language that is suited for refactoring. That usually means it must be Java... nothing comes close in refactorability. It certainly rules out anything that isn't extensively checked by a compiler (eg. Javascript) -- there's always code paths that aren't covered in tests, and you rely on the compiler to warn you of potential problems in those.
Second, I make sure there are good tests available, not just unit tests but also integration tests. This is usually the case on projects I work on, or I won't be sticking around for long.
Third, I do refactors in tiny incremental steps. Usually it's like "remove one method", "remove a parameter", "add/remove an interface", "delete a class", "split a class", "move a method", "make a parameter required by moving it to a constructor", "make something immutable by removing its setter".
As soon as you do one of those, there are gonna be errors detected by the compiler. I ignore the ones in tests and quickly check the ones in real code to see if the refactoring does not have something I did not foresee that would be tough to fix. If there's something unforeseen, I think about what refactoring would be need to be done before this one that would make that easier to fix later.
Once it looks like it will work, I fix the errors in one class, then in its test, then run the test to see if nothing broke. Then I go to next class, until all the errors are gone (usually this takes around an hour for 50-200 errors when I started).
I then run all the tests, and if everything is ok, I make a commit of that refactor step. Then I start with the next incremental refactor step. At any time, I can merge the stuff with master and stop and work on something else, while still having some small benefits of the refactorings done so far.
In this fashion I've refactored existing code bases to use a different time/date system, refactored code to use two different models (instead of a unified one), made models slowly immutable (it's amazing how many problems you discover when you start doing that), etcetera.
I find them equally easy to read. You should just get some more experience with ?: constructs. They're not the same as IF's, which gives you added feedback that only one thing happens (a value is guaranteed assigned), where as in a IF I can do other things, with no guarantee anything was assigned.
I've used both in commercial environments. Since we abstract everything away in Java using ORM layers, you hardly notice the "Oracle" underneath, so it's workable.
However, any direct contact with Oracle DB's reveals their ugly roots -- it seems the entire product is something bolted on top of an engine build in the 80's, with all kinds of quirks that nobody expects anymore of a modern DB. Here are some the quirks I hated:
- VARCHAR empty string is treated as NULL by Oracle. No switch to turn it off anywhere. - Oracle does not do transactional DDL -- that is if I upgrade my database by dropping some tables and moving some data around, and the process fails halfway due to some inconsistency, you're left to sort it out on your own... no rollbacks. - No database types for 32/64 bit integers, so there's often a mismatch with the type in the database and the type in your programs. No boolean type. No date type. - Error messages are (were?) often of the kind "duplicate key violated" -- no additional info which key, which row or columns were involved - Their tools feel like they were build in the 80's -- and the 3rd party tools aren't much better. Both mysql and postgresql have way better tools. - Doing INSERTs in a Table which happens to have some foreign keys can result in "too many open cursors" -- what cursor? The internal one it created itself to check the foreign key? What do I care? This results in code that has to do COMMITS every 50 rows (or whatever you have configured the db with). - Schemas, users, databases... it seems to be all the same in Oracle. - Many settings in Oracle are global for all databases, but many of those settings would be much more useful if they could be set per database
Oracle however seems to have little interest in fixing these things and getting some good will from people that actually have to work with their systems. They only care about selling the big features, never mind that almost nobody needs them.
Try PostgreSQL -- by the time you feel it no longer fits your needs, consult and expert to look at how you do your queries and indices (the #1 cause of bad performance). If it still doesn't fit your needs after that, you might have a reason to try Oracle.
How about it treating empty varchars as NULLs? Good reason to hate it. How about not offering standard data types, like int, long, boolean? Or how about the fact that Oracle still cannot do transactional DDL?
The runtime was actually never particularly bad. Java mostly got its bad rap from poor Swing performance (not as smooth, and not as good looking as the native apps). People perceived that "visual lag" as the language being slow, while in reality it has always been pretty damn fast.
Software can be written to even handle unanticipated events. In many pieces of software, unanticipated stuff happens all the time (time outs, services returning garbage, network failures).
What do you when something unanticipated happens? You come to a stop in an empty lane and activate your warning lights? Or maybe you keep driving, even with that wierd noise you keep hearing and see if it goes away?
Software could do the same, and probably do it with better info than you have.
There's these things called maps. You could click on where you want to go, and the car can drive you to the nearest location there that is accessible by road.
When parking, it could display you a nice image of where to go or park next (maybe even just display you a map of your surroundings gathered with radar or whatever). Click where you want to park, or better yet, where you want to be dropped off.. who wants to wait for your car to park, it can do that by itself.
This stuff simply is not impossible. AI will approach that of humans soon enough, and it will be able to drive cars...
Unfortunately, most people that use Git, used to use SVN. They think that merges solve all problems, as that was the only tool available in SVN.
In Git however, you have two tools to solve your problems. Rebase and Merge. Rebase is just as fundamental a tool as Merge, so one of the first things you should learn is the difference between the two. Once you learn how it works, you'll look at merges as something evil unless it was a merge to integrate something in the main branch.
One of the most compelling reasons to use Rebase is that it makes it possible to make all your merge commits fast-forward commits. What this means is that even though something took 20 days to build in a branch, once you put it back into the main branch you can make it appear as if all the work happened instantaneously and was added as one nicely packed change right on top of the current state of the main branch.
I spotted the mistake. You have branches that last 3 months. In our team we have branches that last at most 1 sprint (two weeks) and often much shorter than that.
A branch should be something short-lived. Split that 3 months of work into much much smaller pieces that can be added to the develop or master branch without breaking anything (if necessary with a functionality switch). In a code base that is not somekind of tangled mess, small refactorings and changes are the way to go. Often we have several refactor and clean-up commits (that donot change functionality) before actually implementing a feature (which after the refactorings and cleanups is often trivial).
No version control system is gonna save you from the merge or rebase problems you'll have after working in your own little bubble for 3 months.
Except of course that the largest security risk really is a modern operating system that still runs on a 1990's kernel that was never designed to be secure.
if [ -d "$MOST_RECENT" ]; then
LINK_DEST="--link-dest=\"$MOST_RECENT\"" fi if [ -f "$SRC/.snapshots-filter" ]; then
FILTER="--filter='merge.snapshots-filter" fi
# function moveBackup {
if [ -d "$1" ]; then
if [ $(((`date +%s` - `stat --format %Y "$1"`) / 60)) -ge $4 ]; then
if [ -d "$2" ]; then
if [ ! -d "$3" ]; then
mv "$2" "$3"
else
rm -rf "$2"
fi
fi
mv "$1" "$2"
fi
fi }
That's because "import wheel" in Python means something different than the similarly written statement in Java.
In Java an import written like that imports only one specific class. In Python it means import everything defined in the wheel "package". You can do the same in Java: import wheel.*
That syntax however is frowned upon, as it makes it less explicit about what you are actually using, and more assumptions must be made to understand the code. Did that class come from "wheel" or did it come from "stoneage", "car", "steam" or "yarn" ?
Anyway, contact me again when a Python snippet can actually survive being send by email.
The semi-colon's mean I can split up a long line of code in nicely lined up parts with just the enter key. Without them, I might be changing what the code does, or I'd need to fix all the parts and add somekind of continue operator.
The fact that those semi-colon's are there is one of those things that makes Java more readable. I don't have to guess that a line will be continued or not (see Javascript). Making it optional just serves to confuse future readers of the code - that's why Java makes these mandatory. You can leave them out, but then you're code means something else.
This and the fact that Java often only gives you one option to write something is exactly what makes the language so readable. Try explaining that though to the Scala / Groovy / Clojure / Hip++ crowd however... they seem convinced that the number of keystrokes it what dictates development speed... perhaps I should sell them Dvorak keyboards...
1) Filter out every item matching the criteria (ie, discard every item less than 10) 2) Filter out every item not matching the criteria (ie, keep every item less than 10)
It is good practice to throw exceptions for even fatal errors. What they've missed however is to add information to the exception about the error (the stuff you propose they should log).
Somewhere way up the call stack you have a catch all exception handler, which is responsible for logging the exception -- so it is not necesarilly the caller who logs this. If the caller cannot handle the exception, it should just leave it and let it bubble up.
This way there's no need to duplicate logging code in every small little function that can't handle certain inputs.
Yes, calculating a 16 byte number over files that are all >17 bytes in size will never result in a false positive...
...or they could just google it again, like you did when you found the answer on SO.
When I want to refactor code, I first make sure we're working in a programming language that is suited for refactoring. That usually means it must be Java... nothing comes close in refactorability. It certainly rules out anything that isn't extensively checked by a compiler (eg. Javascript) -- there's always code paths that aren't covered in tests, and you rely on the compiler to warn you of potential problems in those.
Second, I make sure there are good tests available, not just unit tests but also integration tests. This is usually the case on projects I work on, or I won't be sticking around for long.
Third, I do refactors in tiny incremental steps. Usually it's like "remove one method", "remove a parameter", "add/remove an interface", "delete a class", "split a class", "move a method", "make a parameter required by moving it to a constructor", "make something immutable by removing its setter".
As soon as you do one of those, there are gonna be errors detected by the compiler. I ignore the ones in tests and quickly check the ones in real code to see if the refactoring does not have something I did not foresee that would be tough to fix. If there's something unforeseen, I think about what refactoring would be need to be done before this one that would make that easier to fix later.
Once it looks like it will work, I fix the errors in one class, then in its test, then run the test to see if nothing broke. Then I go to next class, until all the errors are gone (usually this takes around an hour for 50-200 errors when I started).
I then run all the tests, and if everything is ok, I make a commit of that refactor step. Then I start with the next incremental refactor step. At any time, I can merge the stuff with master and stop and work on something else, while still having some small benefits of the refactorings done so far.
In this fashion I've refactored existing code bases to use a different time/date system, refactored code to use two different models (instead of a unified one), made models slowly immutable (it's amazing how many problems you discover when you start doing that), etcetera.
I find them equally easy to read. You should just get some more experience with ?: constructs. They're not the same as IF's, which gives you added feedback that only one thing happens (a value is guaranteed assigned), where as in a IF I can do other things, with no guarantee anything was assigned.
Just wondering, do you have a clustered index on the timestamps already?
For Oracle you need experts. That says enough about their product.
I've used both in commercial environments. Since we abstract everything away in Java using ORM layers, you hardly notice the "Oracle" underneath, so it's workable.
However, any direct contact with Oracle DB's reveals their ugly roots -- it seems the entire product is something bolted on top of an engine build in the 80's, with all kinds of quirks that nobody expects anymore of a modern DB. Here are some the quirks I hated:
- VARCHAR empty string is treated as NULL by Oracle. No switch to turn it off anywhere.
- Oracle does not do transactional DDL -- that is if I upgrade my database by dropping some tables and moving some data around, and the process fails halfway due to some inconsistency, you're left to sort it out on your own... no rollbacks.
- No database types for 32/64 bit integers, so there's often a mismatch with the type in the database and the type in your programs. No boolean type. No date type.
- Error messages are (were?) often of the kind "duplicate key violated" -- no additional info which key, which row or columns were involved
- Their tools feel like they were build in the 80's -- and the 3rd party tools aren't much better. Both mysql and postgresql have way better tools.
- Doing INSERTs in a Table which happens to have some foreign keys can result in "too many open cursors" -- what cursor? The internal one it created itself to check the foreign key? What do I care? This results in code that has to do COMMITS every 50 rows (or whatever you have configured the db with).
- Schemas, users, databases... it seems to be all the same in Oracle.
- Many settings in Oracle are global for all databases, but many of those settings would be much more useful if they could be set per database
Oracle however seems to have little interest in fixing these things and getting some good will from people that actually have to work with their systems. They only care about selling the big features, never mind that almost nobody needs them.
Try PostgreSQL -- by the time you feel it no longer fits your needs, consult and expert to look at how you do your queries and indices (the #1 cause of bad performance). If it still doesn't fit your needs after that, you might have a reason to try Oracle.
How about it treating empty varchars as NULLs? Good reason to hate it. How about not offering standard data types, like int, long, boolean? Or how about the fact that Oracle still cannot do transactional DDL?
The runtime was actually never particularly bad. Java mostly got its bad rap from poor Swing performance (not as smooth, and not as good looking as the native apps). People perceived that "visual lag" as the language being slow, while in reality it has always been pretty damn fast.
Asking things twice makes sense when the field hides what you typed, like password fields. It makes zero sense for email fields.
A comment like fixes bug XXX is useless -- instead, add a Unit test that verifies that bug XXX is fixed.
Software can be written to even handle unanticipated events. In many pieces of software, unanticipated stuff happens all the time (time outs, services returning garbage, network failures).
What do you when something unanticipated happens? You come to a stop in an empty lane and activate your warning lights? Or maybe you keep driving, even with that wierd noise you keep hearing and see if it goes away?
Software could do the same, and probably do it with better info than you have.
There's these things called maps. You could click on where you want to go, and the car can drive you to the nearest location there that is accessible by road.
When parking, it could display you a nice image of where to go or park next (maybe even just display you a map of your surroundings gathered with radar or whatever). Click where you want to park, or better yet, where you want to be dropped off.. who wants to wait for your car to park, it can do that by itself.
This stuff simply is not impossible. AI will approach that of humans soon enough, and it will be able to drive cars...
Since we explain how to use Git to non-developers (using TortoiseGit), I call bullshit on this.
The log window in TortoiseGit is usually an excellent way to show people what happens and show them what the actions they take really do.
Unfortunately, most people that use Git, used to use SVN. They think that merges solve all problems, as that was the only tool available in SVN.
In Git however, you have two tools to solve your problems. Rebase and Merge. Rebase is just as fundamental a tool as Merge, so one of the first things you should learn is the difference between the two. Once you learn how it works, you'll look at merges as something evil unless it was a merge to integrate something in the main branch.
One of the most compelling reasons to use Rebase is that it makes it possible to make all your merge commits fast-forward commits. What this means is that even though something took 20 days to build in a branch, once you put it back into the main branch you can make it appear as if all the work happened instantaneously and was added as one nicely packed change right on top of the current state of the main branch.
I spotted the mistake. You have branches that last 3 months. In our team we have branches that last at most 1 sprint (two weeks) and often much shorter than that.
A branch should be something short-lived. Split that 3 months of work into much much smaller pieces that can be added to the develop or master branch without breaking anything (if necessary with a functionality switch). In a code base that is not somekind of tangled mess, small refactorings and changes are the way to go. Often we have several refactor and clean-up commits (that donot change functionality) before actually implementing a feature (which after the refactorings and cleanups is often trivial).
No version control system is gonna save you from the merge or rebase problems you'll have after working in your own little bubble for 3 months.
To which you respond: "Sure, here's the keyboard and a pre-configured develop environment".
No serifs.. fail.
Except of course that the largest security risk really is a modern operating system that still runs on a 1990's kernel that was never designed to be secure.
...because teachers need to be able to decipher the code afterwards.
#! /bin/bash
# stop on errors
set -e
scriptname=$(basename $0)
pidfile="/var/run/${scriptname}"
# lock it
exec 200>$pidfile
flock -n 200 || exit 1
pid=$$
echo $pid 1>&200
# Rest of code
SRC=$1
SNAPSHOTS=$SRC/.snapshots
MOST_RECENT="$SNAPSHOTS/1 hour ago"
RSYNC="/root/rsync-HEAD-20130616-0452GMT/rsync"
RSYNC_OPTIONS="-ah --no-inc-recursive"
LINK_DEST=""
FILTER=""
if [ -d "$MOST_RECENT" ]; then .snapshots-filter"
LINK_DEST="--link-dest=\"$MOST_RECENT\""
fi
if [ -f "$SRC/.snapshots-filter" ]; then
FILTER="--filter='merge
fi
CMD="$RSYNC $RSYNC_OPTIONS $FILTER --exclude=.git --exclude=.snapshots $LINK_DEST \"$SRC/\" \"$SNAPSHOTS/.temp/\""
mkdir -p "$SNAPSHOTS/.temp"
eval $CMD
touch "$SNAPSHOTS/.temp"
#
function moveBackup {
if [ -d "$1" ]; then
if [ $(((`date +%s` - `stat --format %Y "$1"`) / 60)) -ge $4 ]; then
if [ -d "$2" ]; then
if [ ! -d "$3" ]; then
mv "$2" "$3"
else
rm -rf "$2"
fi
fi
mv "$1" "$2"
fi
fi
}
moveBackup "$SNAPSHOTS/around 3 months ago" "$SNAPSHOTS/around 6 months ago" "$SNAPSHOTS/around 9 months ago" 259110
moveBackup "$SNAPSHOTS/around 1 month ago" "$SNAPSHOTS/around 2 months ago" "$SNAPSHOTS/around 3 months ago" 86310
moveBackup "$SNAPSHOTS/about 15 days ago" "$SNAPSHOTS/about 23 days ago" "$SNAPSHOTS/around 1 month ago" 33030
moveBackup "$SNAPSHOTS/about 7 days ago" "$SNAPSHOTS/about 11 days ago" "$SNAPSHOTS/about 15 days ago" 15750
moveBackup "$SNAPSHOTS/about 3 days ago" "$SNAPSHOTS/about 5 days ago" "$SNAPSHOTS/about 7 days ago" 7110
moveBackup "$SNAPSHOTS/24 hours ago" "$SNAPSHOTS/about 2 days ago" "$SNAPSHOTS/about 3 days ago" 2790
moveBackup "$SNAPSHOTS/8 hours ago" "$SNAPSHOTS/16 hours ago" "$SNAPSHOTS/24 hours ago" 870
moveBackup "$SNAPSHOTS/4 hours ago" "$SNAPSHOTS/6 hours ago" "$SNAPSHOTS/8 hours ago" 270
moveBackup "$SNAPSHOTS/2 hours ago" "$SNAPSHOTS/3 hours ago" "$SNAPSHOTS/4 hours ago" 90
if [ -d "$SNAPSHOTS/1 hour ago" ] ; then
mv "$SNAPSHOTS/1 hour ago" "$SNAPSHOTS/2 hours ago"
fi
mv "$SNAPSHOTS/.temp" "$MOST_RECENT"
chown --reference="$SRC" "$SNAPSHOTS"
chown --reference="$SRC" "$MOST_RECENT"
That's because "import wheel" in Python means something different than the similarly written statement in Java.
In Java an import written like that imports only one specific class. In Python it means import everything defined in the wheel "package". You can do the same in Java: import wheel.*
That syntax however is frowned upon, as it makes it less explicit about what you are actually using, and more assumptions must be made to understand the code. Did that class come from "wheel" or did it come from "stoneage", "car", "steam" or "yarn" ?
Anyway, contact me again when a Python snippet can actually survive being send by email.
The semi-colon's mean I can split up a long line of code in nicely lined up parts with just the enter key. Without them, I might be changing what the code does, or I'd need to fix all the parts and add somekind of continue operator.
The fact that those semi-colon's are there is one of those things that makes Java more readable. I don't have to guess that a line will be continued or not (see Javascript). Making it optional just serves to confuse future readers of the code - that's why Java makes these mandatory. You can leave them out, but then you're code means something else.
This and the fact that Java often only gives you one option to write something is exactly what makes the language so readable. Try explaining that though to the Scala / Groovy / Clojure / Hip++ crowd however... they seem convinced that the number of keystrokes it what dictates development speed... perhaps I should sell them Dvorak keyboards...
Ah.. the word "filter" again.
filter (10)...
What does it mean?
1) Filter out every item matching the criteria (ie, discard every item less than 10)
2) Filter out every item not matching the criteria (ie, keep every item less than 10)
It is good practice to throw exceptions for even fatal errors. What they've missed however is to add information to the exception about the error (the stuff you propose they should log).
Somewhere way up the call stack you have a catch all exception handler, which is responsible for logging the exception -- so it is not necesarilly the caller who logs this. If the caller cannot handle the exception, it should just leave it and let it bubble up.
This way there's no need to duplicate logging code in every small little function that can't handle certain inputs.