Created
November 19, 2010 22:12
-
-
Save jimweirich/707290 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# I was asked for an example of code that was DRY, but still had an | |
# instance of Connascence of Algorithm going on. | |
# | |
# Consider the following code. I think most people would agree that | |
# the code is fairly DRY in the sense that there is no duplicated | |
# code. | |
# | |
require 'digest/sha1' | |
SALT_SIZE = 2 | |
def generate_salt | |
(1..SALT_SIZE).map { rand(26) }.map { |i| ("a".."z").to_a[i] }.join | |
end | |
def join_with_salt(encoded_string, salt) | |
salt + encoded_string | |
end | |
def parse_salt(encoded_password) | |
encoded_password[0...SALT_SIZE] | |
end | |
def encode_password_with_salt(password, salt) | |
digest = Digest::SHA1.new | |
digest << (salt + password) | |
join_with_salt(digest.hexdigest, salt) | |
end | |
def encode_password(password) | |
salt = generate_salt | |
encode_password_with_salt(password, salt) | |
end | |
def check_password(password, encoded_password) | |
salt = parse_salt(encoded_password) | |
re_encoded_password = encode_password_with_salt(password, salt) | |
encoded_password == re_encoded_password | |
end | |
# Now think through the following changes to various algorithms in the | |
# example: | |
# | |
# (1) Change the hash algorithm from SHA1 to SHA256 | |
# | |
# It's a one line change to the encode_password_with_salt method. | |
# DRY, but no CoA. | |
# | |
# (2) Change the length of the salt. | |
# | |
# Not sure if this counts as an _algorithm_ change, but you need only | |
# change the size of SALT_SIZE in one line. DRY and no CoA. | |
# | |
# (3) Change the algorithm that joins the salt to the encoded | |
# password. | |
# | |
# Suppose you want to put the salt value at the end of the encoded | |
# password rather than the beginning. That requires a change to | |
# "join_with_salt". However to get the code to work, you must also | |
# change the "parse_salt" method so that it picks up the salt at the | |
# same place. Although the code is DRY (in that there is no | |
# duplicated code), there is still Connascence of Algorithm between | |
# the "join_with_salt" and "parse_salt" method. | |
# | |
# SIDE NOTE: You could argue that the code isn't DRY in the Thomas and | |
# Hunt sense: "Every piece of knowledge must have a single, | |
# unambiguous, authoritative representation within a system". For | |
# certainly the knowledge of how the salt is joined to the encoded | |
# password is represented in two separate methods. I'm not sure the | |
# is an example of CoA that is also DRY in the Thomas/Hunt sense. | |
# -------------------------------------------------------------------- | |
require 'test/unit' | |
class TestPasswordEncoding < Test::Unit::TestCase | |
def test_passwords_can_be_round_tripped | |
encoding = encode_password("password") | |
assert check_password("password", encoding) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment