こんな商品コードはダメだ

# はじめに

システム設計、とりわけデータベースの設計を行う際にいつも頭を悩ませるのはコード設計です。
例えば商品コード、仕入れ先コード、工場コード、色コードなど色々あります。

これらのコードに対してどのように体系立てて採番をしていくかがコード設計ですが、
一度決めて運用してしまったら再定義は不可と言えるほどインパクトが大きくなるので、慎重に行わなければいけません。

ここでは私が実際に見てきた**ダメな商品コード**のアンチパターンを紹介していきます。

# 商品コードのアンチパターン

## 日本語（全角文字）

```csharp
"鉛筆0001" // ウソみたいだろ…これ商品コードなんだぜ…
```

いわゆる2byte文字をコードに使うことによるアンチパターンです。初めてこれを見たときは「うえぇぇ…」という感想しか出なかったものです。
しかし商品コード登録の画面に一切バリデーションが掛かってなかったり、「日本語のほうが分かりやすいから」という理由で日本語の商品コードを作ってしまったりと、条件が揃ってしまうと安易に作られてしまいます。

まず一番の問題は「表記の揺れ」です。
システムで商品を検索する際のキーになるのが商品コードに期待することですが、全角文字が入ると「見た目ほとんど同じだけど検索できない」というような問題がよく起きます。

```csharp
"Ａ00001" // 全角アルファベットだったり
```

```csharp
"タッピングねじ・黒色" // かな・カナ・漢字の合わせ技だったり
```

つぎに文字コードの問題です。
「正しい文字コードを指定すれば問題ない！」と言えるかもしれませんが、逆に言うと「常に正しい文字コードを指定しなければならない負担がある」とも言えます。それに商品コードが渡る全ての個所をコントロールできるとも限りません。`SJIS`しか許さないシステムや環境があるかもしれません。

半角カナや機種依存文字も基本的には同じ理由で避けるべきです。

以下、問題の具体例を列挙します。

- 商品コードをキーにExcelで`VLOOKUP`しようとしたら表記揺れでヒットしない。
- 商品コードを一次元バーコードに埋め込めない。
- 商品コードを二次元バーコードに埋め込んだら読み込んだ時に文字化けする。
- `ASCII`しか扱えないシステムに登録できない。
- パッと見コードに見えない。
- 半角カナの商品コードをメールで送ったら全角になって届いた。
- 口頭で商品コードを伝える際、「タッピングは全角カナで、ねじは全角ひらがなで…」ととにかく面倒くさい
- 手書きメモで商品コードを伝える際、正確に読めるか自信が無い。

## カンマ（`,`）

```csharp
"A0,001"
```

商品コードをcsvで扱う際に予期しない場所で区切られてしまいます。
フィールドを""で囲えば回避できますが、例によってcsvの出力環境を全てコントロールできるわけではありません。

> 個人的にはcsvファイルでデータを扱うこと自体がかなり嫌いです。
[こういった危険性](http://blogs.itmedia.co.jp/bozel/2011/03/csv-6b63.html)を周知徹底しないと、普通の事務職の方々は容易にデータを破壊してしまいます。
あとソートを掛けるとデータ構造が破壊されたり、空行を開けないとデータ構造が破壊されたり、独自ルールがあるcsvファイルが多すぎます。これらに個別に対応するのにはもう疲れました…


## 空白スペース

```csharp
"A 10001" // 半角スペースと全角スペースが入り乱れると更にカオス
```

商品コードに空白スペースが含まれると、商品コードをファイル名やディレクトリ名として利用するときに予期しない場所で区切られる危険性があります。
ほとんどの場合パスを`""`や`''`で囲えば回避できますが、これも負担を考えると空白スペースを避けたほうが簡単です。

しかしコードを空白で区切っているようなものはまだまだ雑魚レベルです。
本当の敵は前後に空白が入ったコードです。

```csharp
"  A10001" or "A10001  "
```

これらは頻繁に「見た目ほとんど同じだけど検索も照合もできない」という現象を引き起こします。
また、固定長ファイルにデータを入れた時、最悪判別不能に陥ります。

```csharp
// 20桁の固定長フィールドに商品コードを入れる

"A10001              " // 全部
"A10001              " // 末尾空白がある
"A10001              " // 桁数の異なるコードだよ
```

### 何故発生するのか

例えば末尾空白の商品コードですが、ユーザーが意図的に登録しようとすることはまずないでしょう。
発生する原因は「商品コードを登録する際、商品コードを**コピペ**で入力するから」です。
ペーストされた商品コードに末尾空白が含まれていても、カーソルを合わせなければまず気付きません。

システム側で末尾空白は弾く（もしくはトリムする）よう、バリデーションをしっかりする事が大事です。

> 半角スペース区切りの商品コードは丁度今のシステムで扱っていますが、意外となんとかなってます。
しかしいつどこでトラブってもおかしくないので、ヒヤヒヤもんです。

## 制御文字

さすがに`STX`とか`ETX`とかお目にかかったことは無いですが、たまにあるのが**タブ**です。

タブは上記の「カンマ」と「空白スペース」を複合したような問題を起こします。
改行コードなんかもテキストへの出力系が全滅する威力を持ってますが、流石に見たことないですね。

> カンマを使いたいが為にtsvファイルを採用しているシステムを見るが、他の場所で死ぬからやめてほすぃ…

## 記号

```csharp
"*/A+1-0%0#0.1\\" // なんだこれ…
```

- `\/:*?"<>|`はファイル・ディレクトリ名に使用できません。  
コードをそのままファイル名、もしくはディレクトリ名として利用できなくなります。
- `*`はワイルドカードとしてよく使用されます。  
`*`が含まれるコードを検索しようとすると予期しない検索結果が出る場合があります。  
また、これもファイル名・ディレクトリ名に使えないですね。
- `%_[`はSQLでエスケープが必要です。
- `&<>"'`はXMLでエスケープが必要です。
- `"<> &`はHTMLでエスケープが必要です。
- 他にもいろいろあると思います…

記号は常にエスケープを考えなければならないし、常にエスケープできるとも限りません。
可能な限り利用を避けましょう。

## アルファベット`ILO`

```csharp
"Al0O0I"
```

- アルファベット`ILO（ilo）`は数字と見間違え易い。実際`O`と`0`は何度も見間違えたことがあります。
- 昔「コードにアルファベットILOiloは禁止」という規約を見たことがあるので入れてみました。
- 商品コードを手書きメモで伝えようとしたときに威力を発揮します。
- アンチパターンというほどではないかも。

> この3文字が使えないという制約は割と厳しいと思うので、自分が設計する場合禁止にすることは無いです。

## 有意コードを分割する

```csharp
"A0001" -> "A" + "0001" // Aには商品区分などの意味がある
```

`A0001`という商品コードについて「商品の区分を判断する為に先頭1文字`A`を確認しなければならない」という読み方をしなければならないとしたら危険信号です。
ソースコードの中に商品コードを分割して区分を取得する処理（`substring`とか`slice`とか）が散乱すること請け合いです。
また、商品コードは得てして複数のシステムで横断的に使用されるので、共通処理でまとめるといった対応も難しいです。

この場合有意コードであることがダメというわけではありません。コードをコードのまま扱わずに分割する必要があるという点がダメで、有意コードであってもシステム側からは無意コードと変わらない扱いをしなければならないということです。
回避策として商品区分のフィールドをきちんと持ちましょう。

||商品コード|商品区分|
|:-:|:-:|:-:|
|BAD|A0001|-|
|GOOD|A0001|A|

### 有意コードと無意コード

商品コード自体が意味を持つか持たないかで有意コードと無意コードに分けられます。

例えば`A0001`という商品コードがあるとします。
この時`A`が商品の種類を表す区分値で、`0001`が商品を識別する為のコードだとしたらこれは**有意コード**です。

例えば`10001`という商品コードがあるとします。
この時数字に意味はなく、`10001`という商品をただ識別する為のコードであればこれは**無意コード**です。


## 0埋めした数値コードを使う

```csharp
"0000001"
```

これの問題点は`0000001`は**数値**ではなく**文字列**であるという事です。

一番問題が顕著になるのはExcelで表示したときです。0埋めが解除されてしまい`1`という数値で表示されます。
意図しないデータの置き換えが発生する[この問題ですね](http://blogs.itmedia.co.jp/bozel/2011/03/csv-6b63.html)。

このフォーマットを採る場合は大抵連番管理なのでインクリメントしたいと思いますが、DBに格納する場合も文字列で入れるのでオートインクリメントできません。その為、プログラムの中で自力でインクリメントしなければなりません。
DBによっては`ZEROFILL`オプションでオートインクリメントできるものもありますが、DB依存ですのであまりよろしくないです。

簡単な回避策として、最大桁を`1`から開始すれば良いです。

```csharp
1000001
```

この場合数値としてDBに格納できるので、オートインクリメントもできます。


## 人工キーをそのまま商品コードとして使う

「人工キーはユニークなんだし、商品を識別できるんだし、これがそのまま商品コードでいいじゃん！」という発想ですね。

数値のみのコードが可変長である場合、**一目で商品コードであることが分かり辛い**という問題があります。

```csharp
1
3
254
88372
7300001 // 全部商品コードだよ
```

また手書きのメモの話ですが、「`3`の在庫が`4`足りないよ」とだけメモがあった時、`3`が商品コードだとすぐに認識できるでしょうか？

数値のみであっても固定長の商品コードであれば、比較的認識しやすいです。

```csharp
10000001
10000003
10000254
10088372
17300001 // 8桁固定長の商品コード
```

「`17300001`の在庫が`4`足りないよ」であった場合、「8桁の数値は商品コードだ」とすぐ分かります。
ただし固定長の場合扱える商品数が有限になってしまうので、何桁にするかは十分な検討が必要です。[^1]

<!--
## 可変長の有意コード

```csharp
"A1"
"A10"
"A3B2"
```

- 商品コードの異常に気付き辛い。
- 固定長ファイルで扱う際スペースが含まれ、トリムする必要がある。
-->

# おまけ

商品コード自体の設計ではないけどアンチパターン2件と、
私が「なるほどな！」と思った設計の一例を紹介したいと思います。

## 一貫した商品コードを使用しない

```csharp
"A10001" // システムAではこの商品コード
"10001"  // システムBではこの商品コード
```

同じ商品を指しているのに、システムによって異なる商品コードを使用しているパターンです。
「もう識別できないじゃん。論外じゃん」という気分になりますが、以下のような状況で起こり得ます。

- `A10001`という商品コードでシステムAに登録していたが、新たに導入したシステムBでは商品コードに数値しか許可されていない。[^2]
- システムAでは通常の商品コードで登録してあり、システムBでは客先の商品コード（もしくは外注先の商品コードなど）で登録されている。

コントロール出来うる範囲では一貫した商品コードを使用することが大原則ですが、上記のような状況が不可避の場合は「商品コード変換テーブル」をDBに持つことで対応するしかないと思います。

## 商品コードをテーブルの主キーにする

最近「テーブルの主キーは人工キー派」になりました。
商品コードはユニーク制約とインデックスを付けて主キーにしないほうがいいです。

何故かというと必ず「商品コードを変更したい」という要望が必ず出るからです。（特に有意コードにした場合）
商品コードを主キーにすると、DBのあらゆるテーブルに商品コードが点在するようになってしまいます。
特にトランザクションテーブルに商品コードを含むレコードがどんどん蓄積されていくと時間が経つほど訂正が難しくなります。

商品コードが主キーでない場合は、テーブル（商品マスタ）の１カラムでしかないだけなので簡単に修正できます。
トランザクションテーブルのレコードにも影響ありません。


## 固定長のランダム数値コード（非アンチパターン）

```csharp
45680813
90245032
10000254
33077200
```

なんの変哲もない数値コードですが、「連番管理しない」という特徴で「入力ミスの排除」と「入力の高速化」を重視したコード設計です。

- 類似商品であっても商品コードは全く異なる数値になるので、一覧表示したときに連番表示にならず誤認し辛いです。
- 商品コードをシステムに入力する際、一文字打ち間違ってもエラーなり、異なる製品を意図せず入力してしまう危険性が非常に低いです。チェックディジットを採用すると更に危険性が下がります。
- 数値だけのコードなので、慣れるとテンキーでの入力が非常に早いです。

とにかく大量のデータをシステムに打ち込まないといけない！という業務に特化した商品コードです。[^3]

実際運用してみて、アンチパターンで紹介したようなコードよりは明らかに扱いやすく堅牢性が高いと感じました。


# さいごに

書き出してみたら「当たり前じゃん」というような内容が大半になってしまいましたが、
そんな「当たり前」が出来ていないようなシステムが数多くあるのも実情だと思います。

最初にも書きましたがコード設計は一度運用を始めたら実質訂正不可です。
その為、一度失敗してしまうと苦しみは結構長く続くと思います。

少しでも私と同じ苦しみを味わう人が少なくなりますように…


# 参考

[コード設計の話](http://hot-heart-cool-mind.seesaa.net/article/393131307.html)
[有意コードと無意コード](http://witworks.blogspot.jp/2010/05/blog-post_28.html)
[システムの寿命はコードで決まる！ (1/3)](http://www.atmarkit.co.jp/ait/articles/0606/08/news124.html)
[ERPの落とし穴part5～コード設計の難しさ](http://forza.cocolog-nifty.com/blog/2013/06/erppart4-8b67.html)
[ビジネスの変化に強いデータモデルを作る (1/3)](http://www.atmarkit.co.jp/ait/articles/0911/11/news108.html)

[^1]: とはいえ人工キーが無限というわけでも無いですが
[^2]: アルファベットがダメというのは無いと思うけど、日本語や記号がダメはわりとあると思います。
[^3]: そういう業務自体どうなの？という気持ちもありますが