U+FFFF以上の文字ってなんや → サロゲートペアってなんや → Spring Web Services が言うことを聞きません! とかで一日潰れた話
表題のような感じなのですが、これまで理解が曖昧だったUnicodeとか何とかが今までよりわかったのでメモ。
尚、こちらのサイトを非常に参考にさせていただきました。
コードポイントとは 文字コードとは
今日覚えた単語その一。Unicodeに限らず、文字をコンピュータ上で表現する際、1つの文字に1つの数値を対応させるわけですが、この文字に対応する数値をコードポイントというそう。
いままでASCIIコードとか呼んでました。
そして、文字と数値の割り当てのルールのことを「文字コード」と言うんだそうです。
Unicodeとは から UTF-XXは何が違うんじゃ という話へ
Unicode誕生
文字コードが乱立したため、あるコードポイントで表現される文字が、文字コードによって、てんでばらばらという状況に。
ややこしいから、ひとつの統一した文字コードをつくろう! ということで「Unicode」が作られることになりました。
Unicodeを使うときは、U+12ABのように16進数の先頭にU+をつけて表すことにして、
U+0000~U+FFFFの216パターンで世の中の文字を表そう!ってことでUnicode1.0が生まれたそうです。
サロゲートペアの誕生
ところが、特にアジア圏の文字を表そうとしたらこれでは全然足りなくて、U+10FFFFまでUnicodeを拡張することになりました。
しかしこれだと1文字表現するのに21bit必要で、2nじゃないのでとっても切りが悪いという事態に。
そこで、もともとUnicode1.0で使われていた16bitでなんとか表現するために、
たまたま未使用だったD800H~DFFFHを2つ組み合わせ、32bitを使ってU+10000以上を表現するという、むりくりな方法が発明されたそうです。これが「サロゲートペア」。
で、U+0000~U+FFFFの文字をBMP(Basic Multilingual Plane, 基本多言語面)、U+010000~U+10FFFFまでの文字をSMP(Supplementary Multilingual Plane, 追加多言語面)と呼ぶことにしたそうです。
UTF-XX
上記のような経緯を踏まえ、実際にデータ上でどう表すかという、エンコード方式がいくつか考えられたそうです。
UTF-16 → 上の通りのサロゲートペアをそのまま実装した、一文字に16bit使うエンコード方式。一番正直。
UTF-32 → サロゲートペアはややこしいので、すべての文字に対して32bitを割り当てた方式。一番わかり易いけど、一番無駄が多い。
UTF-8 → 文字に合わせて、8bit, 16bit, 24bit, 32bitのどれかを使うようにしている。Unicodeの中では無駄が一番少なく、最も普及している。
JavaでのSMP
少し話変わってJavaのお話。
突然ですが、Javaのプリミティブ変数のchar型は、上限が (char) 0xFFFF なので、なんとSMPに割り当てられた文字が表現できないそうです!
そこで、Java5.0以降、上記のサロゲートペアの考え方を流用して、SMPは、char2つで1文字を表すようにしています。
Javaでのサロゲートペアの扱いについてはこの記事がわかりやすかったです。
Spring Web Serviceが言うことを聞きません!
…と、ここまで説明してようやく私の身に起こったことを。
いろいろありまして、XML上にU+1F6ADのようなSMPを出力する必要がありました。
XMLの仕様的には単純で、数値文字参照を使って 🚭 とか 🚭 と表現できればOKです。
一方、私達のサービスではSpring Web ServiceというSOAPアプリのためのフレームワークを使っています。
こいつ、普段はなかなか便利な子でして、EndPointのレスポンスとしてBeanをReturnすると、そのBeanの階層構造をそのまま反映したXMLを作ってくれるのです。
また、XML上必要なエスケープも自動で行ってくれて、たとえば「&」が文字列の値として含まれていたら、XMLを出力する際にはきちんと「&」と出力してくれる、という具合です。
では、このSpring Web Serviceを使った時に、
レスポンスのBeanに含まれる hoge フィールドに、🚭(U+1F6AD)が入っていたらどうなるか!?
なんじゃこりゃぁ!?
55357 = D83DH, 57005 = DEADHで、サロゲートペアに分解された上で実体参照として表現されています。
おそらく、String hoge 内のcharの配列を頭から順に1文字ずつ処理してるんだろうなぁ… と推測されます。
更に良くないことに、D800H~DFFFHはUnicodeでは単独で存在できない文字なので、上記�や�はXML上使えない文字で、これらが含まれたXMLは正しくパースされません。
結局現状で、XMLを破壊しないためには、Character#isSupplementaryCodePoint methodを使って、SMPを除外する以外方法がなさそう。
うーむ…。
おまけ Oracle上では
じゃあこの禁煙マークがOracle(UTF-8エンコードで)に入ってた場合どうなるか
select ascii(hoge) from hoge_table;
-> 4036991661
おうふ。
これは、4036991661 = F09F9AADH でして、SMPをUTF-8で表現する場合、やたらややこしいルールで、やたらでかい数値に変換されるため…みたいです。
Javaでequalsを実装するとき -失敗するとcontainsが動かない-
hashCodeはもちろん実装しましょう。
で、
public class 俺のクラス {
private String ore;
public 俺のクラス (String ore) {
this.ore = ore;
}
public boolean equals(俺のクラス o) {
return ore.equals(o.getOre());
}
...
}
ってやったらダメ。一見まともに動くんですが…
俺のクラス o1 = new 俺のクラス("hoge");
俺のクラス o2 = new 俺のクラス("hoge");
俺のクラス o3 = new 俺のクラス("foo");
// true
o1.equals(o2);
// false
o1.equals(o3);
Correctionのcontainsがちゃんと動かない。
Set<俺のクラス> os = new HashSet<>();
os.add(o1);//もちろんtrue
os.contains(o1);
//trueになるかとおもいきやfalse
os.contains(o2);
正しいequalsの実装はこう。
public boolean equals(Object o) {
ほげほげ
}
こんなところでしばらくハマってしまった…
ちなみにはまってる間、色々調べていてわかったのですが、
HashSetって内部的には単一のObjectをValueにもってるHashMapなんですね。(java7だと)
Mapのキーの管理にSet使うならわかるんだけど実態は逆で不思議。
org.junit.Assert.assertEquals って
中で比較してんのは equals じゃなくて == なのね…
ただしStringだけはちゃんと比較してる模様。
XP祭り2014に参加してきました! -Agileってなんなんだろう が少しわかったお話- #xpjug
Agile という ものにたいしてここ最近モヤモヤしていたものが、
もやもやの輪郭が少し見えてきた という話をまとめます。
アジャイルを手放して得られたこと から
の続きから。
前の記事で書いたように、アーキテクチャとプロジェクトマネジメントはとても大事で、案件中にホイホイ変えるようなものではない。
とはいえ、考えすぎてもわからないことはある。
で、どーするよ ってところから生まれたのがAgileだというお話。
だからとても画期的だった。
けれど課題があって、
一つが全体整合性の軽視。特にアーキテクチャ。
…うわ、これすげーわかる。
いくらテスト自動化とかしてたって、全体のアーキテクチャを根底から作り直すのはとても時間&リスクがかかることなので、避けられるものなら避けるべき。
実際Scrumだって、Sprint 0で計画するんですよね。
アーキテクチャにかぎらず計画などもそうですが、
「不確実だから途中途中で見なおす機会を作る」のがAgileなんであって、「最初から全部なんてわかりっこないから、ちょっとずつ決めてきゃいいんです」ではない。
…ではないのだけれど、このへんわかってない人間が、
これAgileなんでっ って、Agileという言葉を言い訳に使った状態
これをAgileのダークサイドと鈴木さんはおっしゃっていました。
痛い。
痛い痛い痛い。
なんか思う所ある。心がまじいてぇww
おっさん、心臓ザクザク刺された気がしてマジ辛くなったっすよこの瞬間。
じゃあどうするかって言ったら、
大事なのは「良い物を作りたい」という覚悟!
絶対にやってはいけないのは言い訳。
不確実性と、仲間と、プロダクトと、そういったものと向き合う覚悟が大切なのだとおっしゃってました。
それは…多分大丈夫。
基調講演から
順番前後しますが、関将俊さんによる基調講演についてのレポートです。
関さんのお話は初めてお伺いしたのですが、これまで数年間、
XPをカスタマイズして、自分たちにフィットする「昔XPだったやりかた」をやっていたと思っていたけれど、
XPエクストリーム・プログラミング入門
というXPの教科書を読み直したら、実はXPの枠からは出ていなくて、 守破離のまだ守でした というお話。
これを聞いて、あ、今の俺そういう状況なのかもと。
守破離の「離」です!とか偉そうなことを思ってるつもりはないのだけれど、一方で、
スクラムとか、プラクティス多すぎてこれに全部そうとか無理ですわ 勝手にいいとこだけ頂きますわ
みたいのはずっと思ってたり。
もやもやがほんの少し明確になった経験1。
更に関さんのお話で刺さったのが、
「本当の反復は上から下までやること」というお話。
CIで毎日ビルドしてますとか、ちょっとずつ作っていますとか
と言うのは本当の反復ではなくて、ビジネスサイドも巻き込んで、ソフトウェア開発に必要なすべてのプロセスを「反復」して初めて反復であると。
関さんの現場では現在は、時間の単位として「イテレーション」という概念は残っているものの、あまりタイムボックスにこだわらずに、ストーリーの完了までを実施しているそうです。
【中古】 XPエクストリーム・プログラミング入門 ソフトウェア開発の究極の手法 /ケントベック... |
XP祭り2014に参加してきました! -アーキテクチャとプロジェクトマネジメントが少しわかったお話- #xpjug
3年連続でXP祭りに参加してきました。
記憶が薄れないうちに早速レポートを。
一気に書こうと思ったのですが、いろんな言いたいことが混ざってきたのでXP祭りのレポートらしく、イテレーティブに書いていこうと思います。
アジャイルを手放して得られたこと
今回、大変刺激を受けた発表の一つです。
心にグサグサ来たのは後半の「Agileのダークサイド」のお話なのですが、
前半の、「アーキテクチャとは」「プロジェクトマネジメントとは」が、まず私にドはまり。
まさにこのへんが昨今の自分にとって<なんなのかよくわからない>ポイントだったのです。
ということで、まずはこの点から。
- アーキテクチャとはシステムに関わる利害関係者(ユーザ、開発者、プロダクトオーナー、経営者、…)の要望をうまいことあわせたもの
- これをすり合わせるのがアーキテクトのお仕事
- アーキテクトの仕事は、事前的。開発が始まってからアーキテクチャを変えるのは大変。
- アーキテクトは 利用時品質→外部品質→内部品質→プロセス の順で考える。
- プロジェクトマネジメントの要素は3つだけ。計画/計測/調整
- 計画のズレを計測し、調整(ステークホルダーやメンバーと)するのがプロジェクトマネージャの仕事
- 計画は重要だが、プロジェクトマネージャの腕の見せどころは「調整」。
なのでプロジェクト開始後の、事後的な仕事がメイン。 - プロジェクトマネージャは プロセス→内部品質→外部品質→利用時品質
な る ほ ど。
「いろんな利害が対立する中で、いい落とし所を見つけるのが良い設計」というのは、以前に先輩エンジニアの方からも聞いたことがありました。
◯◯さんはこうしてほしいと思っている
それをやったら☓☓システムに影響が出てしまう
ならばこのやり方ではどうだっ
のような、ある種の「調整作業」が設計の本質なのかもと。
一方プロジェクトマネジメントについて。
ガントチャートやらタスクボードやら、広い意味でのプロジェクトマネジメントにはもろもろ「見える化」の方法がありますが、
その目的は「計測」。
そしてその結果を元に「調整」することがプロジェクトマネジメントであると。
自分が最近抱えていたもやもやの一端が少しくっきりしたような気がした学びでした。
発表後半については次の記事で。
更新系のAPIって難しい1 部分更新のはなし
勉強不足なのは承知のうえで、実務上困ってることとか考えていることを今日は書いてみたいと思います。
例えば以下の様なデータを取り扱うREST APIをつくろうとしたとします。
- あるECサイトにおいて、商品の料金を取り扱うAPI
- 商品は既にDBに登録されていて、料金のみこのAPIで扱う
- 1商品あたり、料金は2種類ある。通常料金と、お得意様用料金。
- 2種類の料金は、以下の組み合わせが許容される
初期投入の時は、こういうデータをPOSTすると思います。(Item IDはURIに入れるほうが正しいかも)
{ "itemID": 123,
"rate":[
{"rateType":"通常料金", "value":"1000"},
{"rateType":"お得意様用料金", "value":"800"}
]
}
通常料金のみも可能なので、こういうリクエストもありです。
{ "itemID": 123,
"rate":[
{"rateType":"通常料金", "value":"1000"}
]
}
お得意様料金のみはNGというルールがあるので、このようなリクエストは許容されず、エラーになります。
{ "itemID": 123,
"rate":[
{"rateType":"お得意様用料金", "value":"800"}
]
}
新規登録はとてもシンプルですね。
一方、困るのが、更新の時のRequest設計です。
全部送ってこいやモデル
APIの実装者として一番簡単なのは、「部分更新は認めない」「全部送ってこい」です。
元のデータがこういう状態で
{ "itemID": 123,
"rate":[
{"rateType":"通常料金", "value":"1000"},
{"rateType":"お得意様用料金", "value":"800"}
]
}
こういうリクエストが来たら…
{ "itemID": 123,
"rate":[
{"rateType":"お得意様用料金", "value":"800"}
]
}
通常料金は、「設定なし」に変更される という考え方です。
ですので、既存のデータがどうであれ、このリクエストはNGです。
なぜならこのリクエストを実行すると、お得意様用料金のみ存在し、通常料金が存在しない という、ルール上許容されていない状態になるからです
このような、全更新モデルですと、APIの実装はシンプルになります。
予想される更新結果が全てリクエストに含まれているので、DB上の既存の値がどうあれ、このリクエストに対してルールのチェックをすればいいからです。
一方で、APIの使い手であるクライアントのアプリケーションは不便を強いられます。
くアライアントの実装者は、
「通常料金だけ更新する画面」のを作りたいかもしれません。
そのような場合、クライアントは一旦Get系のAPIで通常料金/お得意様料金双方を取得した上で、通常料金だけ書き換えて、通常料金とお得意様料金双方をAPI送る必要があります。
部分更新モデル
だったら、rateオブジェクトは送られてきたものだけ更新する
というルールにしたらうまく行きそうな気がします。
こういうリクエストが来たら、
{ "itemID": 123,
"rate":[
{"rateType":"通常料金", "value":"1000"}
]
}
お得意様用料金は何もせずに、通常料金だけとにかく1000に更新する。
これなら、クライアントは使い勝手がいいです。
ではこのようなリクエストが来た場合どうするでしょう?
{ "itemID": 123,
"rate":[
{"rateType":"お得意様用料金", "value":"800"}
]
}
この場合少し複雑で、まず、DB上の現在のデータを調べ、通常料金が存在するかしないかを確認します。
通常料金が存在していれば、問題ありません。
一方、通常料金が存在していない場合(つまり、もともとどっちの料金も入っていなかったケース)、このままではお得意様用料金のみ登録されてしまうので、エラーにしないといけません。
同じような話ですがもう一例。
{ "itemID": 123,
"rate":[
{"rateType":"通常料金", "value":null}
]
}
あまり正しくないかもしれませんが、valueにnullがあった場合、その料金を空にするというルールにしたとします。
このようなリクエストが来た場合、やはり既存のデータによって、このリクエストが成功するか失敗するか決まります。
- 既存のデータが通常料金のみの場合 → 成功。料金が何も登録されていない状態になる
- 既存のデータが通常料金・お得意様用料金双方ある場合 → エラー。通常料金のみ消すのはルール違反なので、更新されない。
つまりいいたいのは
このように、APIで取り扱う対象の中にルールが存在した場合に、
- 全部送ってこいやモデル
→リクエストに対してチェックを行えばいいのでシンプルな実装になる。
反面、クライアントは使いづらい。
(更にクライアントとサーバ間で無駄な通信が発生することになる) - 部分更新モデル
→クライアントの実装はシンプル。
反面、APIの実装は複雑になり、バグの温床にもつながる
(今まで触れていませんでしたが、実際に、この手の複雑さが引き起こしているバグを見つけたことがあります)
という問題がありまして、
私が、更新系のAPIって難しいなぁと思っている理由の一つになっています。
…といいつつ、実は自分の中では、これがベターかな?という
解決方法が見えているので、次回はそれについて触れたいと思います。
※古く、かつ実装面に関しては言及されていませんが、IFがどのようにあるべきかについて議論された記事を見つけました。
http://www.infoq.com/jp/news/2010/11/rest-partial-update