意味のコナーセンスは複数のコンポーネントが特定の値の意味に関して一致しなければならないときに生じます。クレジットカード決済を処理するコードを考えてみます。以下の関数はクレジットカード番号が有効かどうかを判定するためのものです。
def is_credit_card_number_valid(card_number):
# Check for 'test' credit card numbers:
if card_number == "9999-9999-9999-9999":
return True
# Do normal validation:
# ...
ここで問題になるのは、このシステムの全体に渡って 9999-9999-9999-9999 がテスト用のカード番号であるという点で一致しなければならないということです。この値が一箇所で変更されれば、他の箇所でも変更する必要があります。
他の例を挙げます。ユーザーのロールが以下のように整数でエンコードされているとします。
def get_user_role(username):
user = database.get_user_object_for_username(username)
if user.is_admin:
return 2
elif user.is_manager:
return 1
else:
return 0
別の場所で、与えられたユーザー名が管理者権限を持つかどうかを確認する必要があるかもしれません。
if get_user_role(username) != 2:
raise PermissionDenied("You must be an administrator")
「マジックバリュー」を名前付き定数に移動し、値の代わりに定数を参照するようにすれば、意味のコナーセンスは名前のコナーセンスへと改善できます。しかしそうしたとしても、名前のコナーセンスの量は増えます(定数を保存するためにまた別の場所を必要とするからです)。
他のよくある例は、None が返り値として使われる場合です。これは何らかのオブジェクトを見つけるタスクを実行する関数でよく起こります。オブジェクトが見つからなければ None を返すというわけです。
def find_user_in_database(username):
return database.find_user(username=username) or None
しかし、この関数はエラーが起きたときにも None を返すかもしれません。
def find_user_in_database(username):
try:
return database.find_user(username=username) or None
except DatabaseError:
return None
この両ケースで問題なのは、セマンティックな意味が None の値に割り当てられていることです。一つのコードベース内で同じ None が複数の異なる意味を持っていると、プログラマーがどの場合にどの意味になっているかを覚えておかなければならなくなります。これは当該の状態を明示的に表すオブジェクトを返すようにすることで、名前のコナーセンスへと改善できます。
def find_user_in_database(username):
try:
return database.find_user(username=username) or ObjectNotFound
except DatabaseError:
return None
エラー発生時の処理に意味のコナーセンスがまだ残っていますが、少なくとも None の値は曖昧でなくなりました。エラー発生時の処理も同じようなやり方で名前のコナーセンスに改善できます。
さらに他のよくある例は、プリミティブな数値型で複雑な値を表現しようとする場合です。決済を処理するコードベースの中にあるこんな行を考えてみてください。
unit_cost = 49.95
コストはどの通貨で表現されているのでしょうか。米ドルでしょうか、それとも英ポンドでしょうか。通貨が異なる場合はコストを足し合わせられないことをどのように保証できるでしょうか。今までの例と同じように、問題はセマンティックな意味がプリミティブ型に与えられていることです。'Cost' 型を作成して異なる通貨間の操作をできないようにすれば、これは型のコナーセンスに改善できます。
unit_cost = Cost(49.95, 'USD')
特にこの問題は「プリミティブ型への執着」と呼ばれることがあり、プリミティブデータ型で複雑なドメインを表現しようとする問題として一般に知られています。