Gauche - ファイル読み込みのサンプル
gaucheでファイルを読み込むやり方のメモ。
一行ずつ読み込んで処理する
(call-with-input-file "/Users/kanno/tmp/codes/a.scm" (lambda (in) (port-for-each (lambda (line) ; 1行ずつ行う何かしらの処理 (print line)) (cut read-line in))))
一度に読み込んで処理する
Shiroさんより教えて頂いた。
@kanno_kanno タイミングが難しいので、with-input-from-fileとport->string-lseqの組み合わせになりますね。
— Shiro Kawai (@anohana) 2017年5月4日
@kanno_kanno 私の方も読み直してて気づきましたが、file->string-lseqというのがあるような書き方でしたね。仮想的にそういうものを作りたくてもファイルを閉じるタイミングが難しいので作れないから遅延リストを使うならport->string-lseqを使ってね、という意図でした。
— Shiro Kawai (@anohana) 2017年5月5日
; 1度に全部読み込んでリストにする (use file.util :prefix file:) (dolist [line (file:file->string-list "/Users/kanno/tmp/codes/a.scm")] ; 1行ずつ行う何かしらの処理 (print line)) ; dolistを使わずfor-eachでもよい (for-each (lambda (line) ; 1行ずつ行う何かしらの処理 (print line)) (file:file->string-list "/Users/kanno/tmp/codes/a.scm"))
ちなみにリストではなく文字列そのまま受け取る場合はfile->string
というのもある。
(use file.util :prefix file:) (file:file->string "/Users/kanno/tmp/codes/a.scm")
GaucheのドキュメントをDashに追加する
背景
Gaucheのドキュメントを見るにはEmacsのinfoから引けるようにしたりブラウザで見たりとあるけど、個人的にはDashで引けると助かる気がしたので追加してみた。
手順
- GaucheのドキュメントHTMLを作成
僕はVM上のcentosでHTMLを作ったけど、Macでやるのも変わらないと思う。
-- text2htmlがなければinstallしておく $ sudo yum install -y text2html -- http://practical-scheme.net/gauche/download.html $ curl -O -L "http://prdownloads.sourceforge.net/gauche/Gauche-0.9.5.tgz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 6307k 100 6307k 0 0 1576k 0 0:00:04 0:00:04 --:--:-- 2091k $ cd Gauche-0.9.5 $ gzip -dc Gauche-0.9.5.tgz | tar xvf - $ ./configure $ make $ cd doc $ make html
これでdoc配下にgauche-ref.html
(英語版)およびgauche-refj.html
(日本語版)が出来る。
以下は日本語版を追加するものとして進める。
- DashのDocset作成
公式の通り。
12 Any HTML Documentation
こちらに沿う。Dashingを使わない場合は以下のサイトの通りでいけるのではないかと思うけど確認はしていない。 自作Dash docsetの作り方 — Alfred + Dashの爆速リファレンス引き環境を拡張する
-- Docset用のsqlite DBとかplistとか作ってくれるやつ -- https://github.com/technosophos/dashing -- 予めGoが必要 $ go get -u github.com/technosophos/dashing $ cd <gauche-refj.htmlが置いてあるディレクトリ> $ dashing create
dashing.jsonの雛形が出来るので編集する。
{ "name": "Gauche", "package": "gauche", "index": "gauche-refj.html", "selectors": { "h1.chapter": "Chapter", "h3.subsection": "Section", "dt b": "Info" }, "ignore": [ "ABOUT" ], "icon32x32": "", "allowJS": false, "ExternalURL": "" }
index
はindex.html的なのを指定するのだけど、gaucheの場合1HTMLファイルしかない- コメントにてセクションごとにファイルを分ける方法を教えて頂いた
make htmls
とすればセクションごとに分割された html を得ることもできますよ。
- コメントにてセクションごとにファイルを分ける方法を教えて頂いた
selectors
は、セレクタにマッチした部分をDashの索引として登録する
編輯したらbuildする。
$ dashing build gauche -- こんな感じでファイルが出来る $ tree gauche.docset/ gauche.docset/ └── Contents ├── Info.plist └── Resources ├── Documents │ └── gauche-refj.html └── docSet.dsidx 3 directories, 3 files
- DashにDocsetを追加する
メニュー
のDocsets
の+
から上記のgauche.docset
を選べば出来上がり。
感想
1ファイルに全て入っているせいかDash上の検索及び表示が若干遅い時があるけど、やりたいことは出来たと思う。
ソースコードの分かりやすさって何だろう
結論はない。
背景
最近またGaucheを勉強し始めているのだが、ドキュメントを読むとdatum
という表現が良く出てくる。
Special Form: case key clause1 clause2 … [R7RS][SRFI-87] keyは任意の式です。clauseは以下の形式でなければなりません。 ((datum ...) expr expr2 …) ((datum ...) => proc)
http://practical-scheme.net/gauche/man/gauche-refj/Tiao-Jian-Shi-.html#g_t_6761_4ef6_5f0f
datum
って何だろうという疑問を持ち、調べると「dataの単数形」だと知る。
Lisp/Scheme界隈では一般的っぽいけど、こっちの世界に詳しくない僕には聞き慣れない言葉だった。
(他にも関数型では一般的?)
一瞬「datum
って分かりにくいな」と思ったのだけど、上記の通り一般的な用語なので単純に僕の知識レベルに要因があるだけだとすぐに思い直し、その勢いで最近感じているコードの可読性についてポエムを書いている。
プログラミングにおいてソースコードは書くより読むことの方が多いはずで、チーム開発をしていれば自分が書いたコードより他人が書いたコードを読むことの方が多い。 将来の自分は他人ということであれば、自分が書いたコードですら時間が経てば他人のコードになる。 必然、コードは(パフォーマンス上の理由がなければ)人が読みやすいように書くべきだ。
では「分かりやすさ」とは何だろう。というのをぼんやり考えることがある。 「面白い」「美味しい」と同じように、当人の経験やスキルや価値観に大きく影響するものであり、普遍的な「分かりやすさ」などないのではないか。
そもそも職業プログラマたるもの、わざと分かりにくいコードを書く人などいるのだろうか。どんなコードであれ本人にとってはそれは分かりやすいのではないか。本人が分かりやすいと思っている以上、周りが分かりにくいと伝えてもピンと来ないのではないか。
ソースコードについて
まずコードレベルの細かいところから言えば、例えば条件分岐1つとっても色々な書き方がある。 (以降の例は擬似コードだがハイライトのためにcode syntaxは一応javaで指定している)
// パターンA function something(type) { if (type == "a") { return "Aだよ"; } else if (type == "b") { return "Bだよ"; } else { return "なんだろうね"; } }
// パターンB function something(type) { var result = "なんだろうね"; if (type == "a") { result = "Aだよ"; } else if (type == "b") { result = "Bだよ"; } return result; }
// パターンC function something(type) { var type_patterns = [ "a" => "Aだよ", "b" => "Bだよ", ]; // 言語によっては存在しない場合のデフォルト値を指定できるが一応愚直に var result = type_patterns[type]; if (result is not null) { return result; } else { return "なんだろうね"; } }
焦点はif文なので関数名、変数名の適当さは無視する。
僕は大概パターンAかCで書く。ロジックを分けたい場合はAで書き、(結果の)データを分けたい場合はCで書くことが多い。が他の判断基準もあるし、最近では後述するように基本的にはチームに合わせる。 ちなみに僕は個人的な好みで言えば最後のelseは書かない方が好きだし、1回だけでシンプルなものであれば条件演算子を使うのが好きだ。
// パターンC function something(type) { var type_patterns = [ "a" => "Aだよ", "b" => "Bだよ", ]; var result = type_patterns[type]; return (result is not null) ? result : "なんだろうね"; }
条件分岐の例を出したが、コードレベルの流派は他にも色々ある。 ループで書くかmap,each,foldみたいなコレクション操作で書くかとか、状態をクラス変数に持って取り回すか引数でもらうようにするかとか。
どこまでメソッドを切るか、というのもコードレベルの話と言えるかもしれない。 以前は1メソッドは短いほどいいと思っていたけど、メソッド呼び出しがあちこち飛ぶと分かりにくいし、OSSとか見ていると長いけど読みやすいコードもあると知った。当然長くて汚い最悪のコードもある。
OSSの場合はパフォーマンスとかの問題でメソッドを分けていない可能性もあるが、とにかくメソッドの行数と可読性は必ずしも一致しないことを学んできた。もちろん適切にメソッドが分かれていれば分かりやすい。が、それに失敗したメソッドは、愚直に長いメソッドより分かりにくいこともあるのではないかという気がしている。 とはいえ個人的には、それでも長いメソッドの中で処理のコメントを書くならそれをメソッドに切ったらいいのではと思うけど、それは僕の「分かりやすさ」であって、万人の「分かりやすさ」ではないのだと思う。
好みといえばフォーマットが典型的だ。言語によってこれだけ規則が違うのは好みの千差万別さを表している。好き嫌いを廃止して(構文が許す限り)全ての言語でフォーマットが統一されていれば各種ツールとかの再発明も必要ないだろうがそうはいかない。 コードを書く上で気持ちよさは大事だし、フォーマットは気持ちよさに影響するのだ。
Goのように公式でツールがサポートされていなくても、最近はほとんどの言語でフォーマットの規約があるしサポートツールもある。問題はプロジェクトがそれに準拠しているかどうかだ。 イメージというか偏見かもしれないが、スタートアップの場合は「大事なのは分かるけど整備している暇があったらプロダクトコード書く」という感じで後回しにされ、大規模とかSIだとそこまで重要視されていないように思う。 ということで、フォーマット規約をきちんとルール化しているプロジェクトにはあまり出会ったことがない。SIerの頃にJava案件でEclipseのフォーマット設定が共有化されていたような記憶はぼんやりある。
他には命名規則にも好みは表れる。例えば何かの配列を表す場合、僕はnameList
よりはnames
(複数形)を好む。でも人によってはList
にした方が(多少文字数は増えても)パッと見て分かりやすいだろう。names
だともしかしたらname
と読み間違えるかもしれない。静的型付けだったらそんなミスは起こらないかもしれないが。
また昔はgetXXX
としていたが最近では単にXXX
でも別に良いと思うようになった。呼び出している側で代入していれば戻り値を返すのは明らかだ。
アーキテクチャや開発手法について
トランザクションスクリプトが好きな人がいればそうじゃない人もいる。OOPやDDDが好きな人もいればそうじゃない人もいる。UMLで設計する人もいれば口頭やplain textだけの設計で終える人もいる。 アジャイルやウォーターフォールといった開発手法もそう。
ここではアーキテクチャ/手法の良し悪しではなくて、あくまでチームメンバーにとって「分かりやすさ」は違うという観点で話している。 どれだけそのアーキテクチャが理論的に優れていても、正しくても、実行・メンテする人が理解していなければ負債でしかない。 難しいアーキテクチャを採用すべきではないということではなく、チームメンバーによって最適解は変わるのではないかという話。
個人的にはちょっと前に読んだ「ユースケース駆動開発実践ガイド」が面白かったのでこれで開発してみたいけど、自分が長期的に開発していくプロジェクトでなければ選択することはまずないだろう。
ドキュメント(コメント)について
プログラミング覚えたての頃はコメントがないと辛かった。知人に「コード読むのに邪魔だからJavadocを全て消すようなプログラム書いた」という人がいてその感覚を理解出来なかった。
ある程度プログラミング出来るようになってくると、今度はコメントはいらないと思うようになった。「結局コードを読むから」というのが理由だった。 当時はほとんど業務コードしか読んでいなかったせいもあって、コメントが正しいことを保証するには結局コードを読む必要があり、コメントに意味を感じなかった。
今では「必要かつ最小限のコメントは書くべき」という立ち位置になっている。リーダブルコードでコメントへの考えが変わったのもあるし、少しずつOSSのコードも読むようになってきて確かにコメントが理解の助けになることを感じてきた。 それまではせいぜい「どうしてもコメント書くならHowじゃなくてWhyのみ書け」という思いだったが、それだけじゃなくて例えば使用例などがあっても良い。 結局「理解の助けになる」なら何でもいいのだ。リーダブルコードにそんなことが書いてあったような気がするし書いてなかったかもしれない。忘れた。
最近、数年前の自分のコードをいじる必要があったが構造とか意図とか全然覚えていなくて、億劫ながらコードを開いたらコメントが随所に書いてあって助かった。たぶんリーダブルコード読んだ後でコメントは大事という認識が強いうちに開発したのだと思う。当時の自分を褒めてあげたい。
そんなコメントもまた「理解の助けになる」の基準は人によって異なるという問題がある。ある人にとっては自明で余計なコメントでも、ある人にとってはそれが助けになるかもしれない。
先人の知恵
リーダブルコードやClean Codeといった書籍もあるし、ネット上に「分かりやすいコード」の情報はたくさんある。 それらに共感できるスキル/マインドセットを持った人だけでチームが組まれているなら、それを基準にすればいいかもしれない。
でもたぶん、そんな簡単なことじゃない。
例えばこれは細かい例だけど=
の位置を揃えるというのがある。
var name = "xx"; var age = 10; var address = "yyy";
僕は最初こういうコードを見た時「うっ」となったけど、今ではこっちの方が見やすいと思っている。けど昔の僕のようにこれが見辛い人もいる。 または他の細かい例だと、僕は「ifの中身が1行なら{}を書かない」というのが嫌いだ。
...前処理 if(pred) return "hoge" ...後処理
一方でrubyなどにあるような後置ifで書くのは好きだ。
...前処理 return "hoge" if pred ...後処理
今の僕はこの通りだが、前述の=
揃えのように好みは変わるかもしれない。
今日自分にとって分かりやすいと思って書いたコードが、将来の自分には分かりにくいコードになっているかもしれない。
つまり先人の知恵に沿って分かりやすさを意識したコードを書いても、それが本当に長期に渡って分かりやすい保証はないのではということだ。
理想論では、そうならないように少しずつ日々のコードをリファクタすればいいのだろう。変わっていく感性/知識にコードを追従させればよい。 でも実際は日々の業務は山盛りでそこまで余裕はないし、それは面白い作業ではないし、何より1つのシステムと共に死ぬことはなくて遠くない未来に別のプロジェクトや会社に移動する。
どうするか
大きくはアーキテクチャから小さくはコードのコメントの書き方まで色々あって、人によって分かりやすさは異なる。 となると何を基準に開発するべきか。
ここ数年の僕のスタンスは「今後そのプロジェクトを長期保守するであろう人に合わせる」ことにしている。 まず既存のコードを軽く読み、コードの雰囲気を知る。それから自分の担当部分のコードを書く時は、8割ぐらいはその雰囲気に合わせる。2割ぐらいは自分の好みを混ぜる。 そこでもしレビューで修正依頼が来たとしても、よほどの理由がない限りは指摘事項に合わせる。そして大概の場合、よほどの理由はない。
同様に他の人のコードをレビューする時も、昔は細かく「僕はこう書く方が好み。なぜなら…」みたいなことも書いたが、今ではほとんどしない。
コードをたくさん読むのはメンテ(保守/機能追加)する人だ。メンテする人が一番大事だ。彼らがストレスなく読み書き出来なければ、どんなに自分にとって分かりやすいコードでも意味がない。 「こう書いたらどうですか」と提案することもあるけど最終判断はその人に任せる。
おわりに
書いてから読み直して気付いたけど、「分かりやすさ」と「読みやすさ」を一緒にして考えている。 これらは別々の話ではないかという気もする。一緒かな。
Go言語でWebアプリを作りかけて辞めた話
3行で要約
私のGoスキル
- A Tour of Goを途中まで
- みんなのGo言語を読んだ
- スターティングGo言語を読んだ
- スクレイピングしてDBに保存する簡単なバッチをGoで書いた
背景
とあるtoB向け受発注のWebアプリを作ることにしました。
開発者は私1人。私が目指した基準は以下のものです。
- 少なくとも3年はメンテできること
- 未来の自分(他人)が読んでも理解できること
- このアプリとガッツリ付き合う予定ではなく、出来れば保守は違う人に回したいし機能追加とかも控えめにしたい(重要)
- デプロイとか運用の手間を簡単にしたい
- ついでに、学んでおくことが有益になりそうな言語がいい
これらを踏まえ、以下の理由からGoを選択しました。
- シングルバイナリでデプロイ出来る
- 大体のことは標準ライブラリで出来るイメージ
- フォーマットとか強制されるのが良い
- 静的型付けによる安心感
- Go書いてみたい
選択したフレームワーク
Webフレームワーク
Revelなどのフルスタックフレームワークは選択肢から外しました。
- シンプルな受発注(CRUD)なのでフルスタックの必要性を感じなかった
- これは後に読み間違いと気付く
- Revelがどこまで活発に開発続くか分からない
- 開発が止まるor人気がなくなると使える人が少なくなるのでメンテ性が下がる
- Go使っているのに少なからずDSLを覚えないといけないことに抵抗があった
「なるべくnet/http
の使い方から外れないシンプルなやつ」ということでGinを選びました。
Echoとかも試しましたが好みでGinにしました。
O/Rマッパー
Webフレームワークと同様でDSLを覚えるつもりはありませんでした。
数年後も使われているか分からないライブラリのDSLを覚えるのはきつい。
かつマイグレーションの機能とかも不要で、単純にレコードを構造体にマッピングしてもらえれば十分でした。
gormは色々できるし途中まで使っていたけど辞めました。
gormに限らないが、吐かれるSQLを確認しながら書くのが本末転倒な感じだったので。
これも薄いラッパーということでsqlxを採用しました。
ちなみに以前は「マイグレーション機能っていいな」と思っていましたが、ここ数年で考えが変わりました。
ツールとしてのマイグレーションってそんな必要なのでしょうか。
upについてはalterとかの差分のsqlファイルがあれば十分ですし、downについては今のところ使った記憶がありません。
開発者間/デプロイ環境間でスキーマのversionが異なるような時に便利なのかもしれませんが、そういう開発案件に携わったことがなく。
マイグレーションに限りませんが、昔は「このツール便利!」と歓喜して導入しようとしていたのが今は「便利だけど覚えるコストと負債になり得るリスクを考慮するとどうだろう」と考えるようになってきました。
若手の頃に嫌だった「保守的なエンジニア」になりつつあるのかな…とちょっと心配になります。保守的になると新しい技術を覚える意欲を失いそうで。
ライブラリ管理
glideを採用。
これはちょっと採用理由を忘れました…。
その他
システム上必要なライブラリ(go-sql-driver/mysql
とか)以外は入れていません。
テンプレートも標準のtemplate
で済ませています。凝った画面ではないので十分です。
Goで書き始めた感想
良かった
- 良い
- ライブラリのコードが読みやすい
- 階層がほとんどフラットだし、importや呼び出しの名前空間が明示的なのも良い
- 「この関数どこで定義されているんだ」みたいな余計な手間がない
- 書いていてストレスがほとんどなかった
- go fmtなども含めて標準ライブラリ/ツールで済むのが良かった
- シングルバイナリは良い
- 静的型付けは良い
- テストコードが同階層にあるのは意外と読みやすかった
- ライブラリのコードが読みやすい
- 良くない
- テストコードでassertはやっぱ欲しい
- エラー処理が面倒
私にとってGoは書きやすいし読みやすいものでした。
「書きやすい」 = 「簡単に書ける」ではありません。
「書きやすい」 = 「書き方に迷わない(迷うことが少ない)」です。
コレクション操作などxx言語ならもっと短く簡潔に書けるのに、というケースはありますが不満は感じませんでした。
簡単に書ける言語だと書き方に迷うことがあるし、レビューでも「こう書いた方がシンプル」みたいな横道にそれがちです。
(今回はレビューする/されることはないので関係ありませんが)
同じ名前空間でもファイルが分かれていることに最初は「うっ」となりましたが、慣れたら気にならなくなりました。
不満としては、テストコードでassert系はやはり欲しいと感じました。
私が手を抜いているのか、テストでは基本的に「AとBが一致しませんでした。Aはこの値でBはこの値です」みたいなメッセージが表示できれば十分です。
これを毎回Errorf(...)
で書くのはDRYじゃありませんし単純に面倒です。
また、ここ数年ずっとスクリプト言語で過ごしてきた私にはエラー処理はちょっと面倒でした。慣れだと思います。
そんなわけですごく良い感じにコードを書いていたのですが、ちょっと要件に合わない選択をしたことに気付きました。
GoでWebアプリを作る上で足りていないところ
主にセキュリティ周りの考慮が漏れていました。
CSRFの対策とかid/pass方式の認証とか二重サブミット防止とか。
revelだとcsrfはプラグインでrevel-csrfというのがあるそうですが、ちゃんと調べていません。二重サブミットとかどうなっているんだろう。
システム要件上id/passの認証が必要なのですがginでは対応していません。
ginも他のフレームワークもBasic認証やOAuthは対応しているのですが。
さすがにこれらを自前で実装するのは再発明が過ぎます。
また細かいところでは、例えば「環境変数(production
/develop
/test
とか)ごとに処理を切り替えたい」とかの処理も必要でした。
典型的なところではDBの接続先を切り替えることです。
環境変数を取ってきたりgin.Mode()
から取り出したり出来ることは出来るんですが、自分で書いていくと結局オレオレフレームワークへ近付きます。
ここに至り、最初の基準で設けた「シンプルに」「DSLに依存したくない」というのは諦めました。
システムの機能がシンプルでも開発環境や非機能要件では色々必要だった、という見積が抜けていました。
とはいえ私の調査不足なだけで実は解決案は転がっているのかもしれません。
開発初期のスピード感と対応範囲、情報量および開発者の多さから、選ばれたのはRailsでした。
当初の目論見と真逆です。
GoでWebアプリを作りかけた感想まとめ
- Goは心地よい(not楽しい)
- (今の自分が)Goで何か作るならバックエンドかバッチか認証や更新操作が必要ないWebアプリ
- 新しい言語をやる時はやはりその道の先輩がいないと相談できなくて厳しい
Vim - previmにヘッダーを隠す設定を追加しました
プレビュー時に画面上部に出ていたヘッダー部を非表示に出来ます。
g:previm_show_header *g:previm_show_header* 型:数値 値が1ならば、プレビュー時にヘッダーを表示します。 ヘッダーには編集しているファイル名と更新日時が表示されます。 値が0ならば、プレビュー時にヘッダーを表示しません。 デフォルトでは1に設定されています。 " .vimrc let g:previm_show_header = 0
.vimrc
にlet g:previm_show_header = 0
を追記して頂ければOKです。