Skip to main content

ログ取得ツール

Category: プログラミング

Gogsその後…

gogsについにPRの画面でマージしたブランチを削除するボタンが出てくるようになった! …何を言ってるかわからない人は、すみませんがこのページを閉じてください。 とにかく便利になった、ということなんですが、gogsの作者は結構厳しい人みたいで、リリースノートから参照されている当該PR(https://github.com/gogits/gogs/pull/3225)にしても、どう見ても必要で実装されていなかった機能の追加をするPR。これをレビューしたあとpingに反応しなかっただけで一度はマージせずにクローズしている。まあパッチに問題があったにしても、こいつはクローズしたあとこの必須機能をどうするつもりだったの?? そして、gogsはいつの間にかforkされてgiteaというプロジェクトができていた。まあどういう関係にあるのか知りませんが。以下は想像のための材料だけど、

  • giteaはgogsをforkしたという説明だけで、gogsとの住み分けとか位置付けを示す文章は見当たらない
  • gogsのメンバーは現在2人で、ex-team members(“元"チームメンバー)と表現されている3人はcontributorsページで見ると貢献度は2-4位
    • 1位の奴はメンバーに残っていて、2-4位だったメンバーがごっそり抜けた感じになっている
    • そしてgiteaのメンバーに2位と3位だった人が入っている
  • ただ、1位の奴のコード行やcommit数は他を圧倒して余りある数字

giteaの方が進んでいるということも言えなくて、今回のマージ済みブランチ削除ボタンもgiteaにはまだマージされていない。gogsでマージされたパッチをcherry-pickして作られたPRはできていた。ただgiteaの方はもうすぐLFSサポートも入りそうな気配で、gogsには当分入らなさそう。まあ、この辺も単なる期待でしかない。 …という感じなので、割と悩ましい状態が続くようだね。

Javaはじめました

私はJavaのコードだけは書けないプログラマ、だったのだが、最近Javaを使わせられるヤバそうな仕事につくことに。Javaと言えばSpring Frameworkです。フレームワーク部分を無駄にXMLで書くやつね。 その前に。 まあしょうがないので最初にHelloWorld、と思ったのですが、これが動かない! なんでHelloWorldが動かないのよ、と。

package main;

public class Main{
  public static void main(String[] args){
    System.out.println("Hello");
  }
}

これをMain.javaという名前で作って、

  • javac Main.java
  • java Main
    • エラー: メイン・クラスMainが見つからなかったかロードできませんでした

ふざけてるんですかこのクソ言語は。いや見つけろよ。そこにあるでしょ。 悩みに悩みました。 Javaのpackage文は厳密なもので、パッケージ名として表現されたシンボルとそこに至るディレクトリ構造は完全に一致していなければなりません。これはpackage文にかぎらず、ファイル名はクラス名と一致していなければならない。そんなクソ仕様でみんなプログラム書いてたのかかわいそうに。 正解はプログラムの中ではなく、外にあったのだ。

GitHubはいかにして最強となったのか

GitHubの日本法人ができたそうです。実際に会社でもGitHub Enterprise(GHE)を散々使ってますし、普通のGitHub.comも使っている信者としては、ああいう良い物が広まっていくのは純粋に望ましいと思う。世の中が良い方向に行くのを感じるからね。Pull Request(PR)はもう一般用語になってる感じ。 だいたい今のプロジェクトホスティング世間はGitHubの1強ですよね。使ってみると圧倒的な便利さが分かるんだ実際。今までとはぜんぜん違う。 何が便利って、UIの良さやGFM(GitHub Flavored Markdown)の良さもあるけど、本質的なところでは、彼らはGitHub Flowという絶妙なワークフローを定義したんですね。これが良く出来てる! 開発がやりやすい。issue&トピックブランチ&PR&マージ。これがないと何も始まらないというほど。 まずGitの人がGit Flowというのを考えた。これはブランチの考え方を整理したもので、悪くはないはずなんだけど、ちょっと複雑だった。そこで彗星のように現れたのがシンプルで強力なGitHub Flow。この最強の武器に、issueをブランチに結びつけてPRに変更できるAPIやコマンド(hub)を出してきたり、GFMやgithub.io(GitHub Pages)、gistもそうだけど、初見で使いやすい上に使えば使うほど使いやすく快適に感じる。 まあ、リリースのファイル名とかはうんこだが。 GitHub.comでも、部外者がPRを出しやすいようになってるよね。変更を加えてPR出す人もめんどくさくないし、出される人も普通にレビューしてコメントしてマージする作業に抵抗を感じない。このレビューの仕組みが揃ってるんですよね。 実は今の仕事ではtrac+svnを強要されているんだけど、せっかくGHEあるんだからGHEにして欲しかった…trac+svnだとまずsvnの問題でブランチ作るのが面倒だし、ブランチマージも大変な作業である上にホンモノのリポジトリ上でやるので緊張感が強すぎる。何よりレビューのフローが入ってない。svnだと権限も厳密ゆえにいい加減になるというか、commitできる人しかcommitできないんだよ(←これ当たり前だけどgitでは当たり前ではない制限)。で、挙句、権限あってもcommitしていいか不安になるというね。レビューのフローがないため、マージも自分でやっちゃうことになる。ブランチの削除も面倒なんで、基本ゴミを残しとくよね。で、もろもろめんどくさいのでブランチ作らずに最後までtrunkで作業し続けたりも。 でも、GitHubならこんなことにはならないんだ。

deep in printf

計算機において偉大と表現できる発明の一つに、printfがある。さまざまな処理系に実装され、現代においても誰も捨てたがらない関数。C++でiostreamとかいう劣化版(性能も悪いし使いにくい!)を推奨する変人がいたが、みんな見向きもしなかったよね。printfえらい。printf考えた人えらい。えらすぎる。 私は以前、printf("%#x", 0)がどう出力されるべきか悩んだことがある。前の会社にいた頃の話。%#xは0xつきで16進の文字列にする、と考えていたため、0x0と出力するのが正しいのではないかと思っていた。今でも使い勝手から考えて0x0のほうがありがたい。しかし、GNU libcや多くの処理系では0の場合は0xをつけずに出力するという挙動をしている。Linuxではdietlibcだと確か0x0だった気がする(いま手元にMacしかないのでよく分からない)。 私が調べただけでも、0x0派はPythonやGolangなどがあり、0派はPerlやRubyなどがある。 まだ調査途中だが、githubのここに置いてある(→ wtnb75/printf)。このリポジトリはいろんな言語のprintfをどう呼び出すのか、printf版のHello World集としても使えるかもしれないな。普通のHelloWorldはみんなputsみたいな感じでフォーマット文字列を使わない書き方にしちゃうから役に立たないと思うこともあり。 0x0派で問題なのは、では8進数のprintf("%#o", 0)は00と表示されるべきか否か、ということになるんじゃないかな。さすがに00は不自然…しかし0x0に対応すると思えば00にするのが正しいよな…というわけで、0x0派は00と出力する処理系が多いが、稀に0x0派なのに0という出力をする処理系もあったり、あるいは0o0という珍しい処理系もあった(0以外の数も0oがつき、0o12みたいになる。これはこれで理にかなっている。今のところRustのみ)。 参考情報としては、IEEEがprintfの仕様を書いていて、そこにはこうある。太字は筆者。

golangでjsonを食ってstruct定義を吐き出すテスト

reflectの練習ですね→json2go.go けっこうこういうのが欲しい人もいるんじゃないかって思って。 だって、JSONを使ったWebのAPIのドキュメントに、リクエスト/レスポンスの例とかがあるでしょう普通? そのAPIをGolangで使おうとなったときに、サンプルのjsonのデータをコピペしてきて、そこから自動でGolangのstructを作って、そのままMarshal/Unmarshalできたら、とても仕事がはかどると思いませんか? こういうのがないと、最初はinterface{}を使って作っていって、型アサーション地獄に陥ってうーんと思いつつ一念発起structに書き換えて、呼び出し側も書き換えてって…みたいなサイクルになるんですよね。最初からstruct作りを自動化しておけば、APIが変更されたときもラクラク更新していけるっていうわけだ。ドキュメントのサンプルがちゃんとAPI変更に追随してくれてれば、の話だがね。

SQLで採番

ユニークな番号を採番したいケースがありますよね。SQLでやれば複数のサーバ間でユニークにできる。UUIDは重ならないとかいう話もあるが、実際システムでユニークであることを保証しようと思えば乱数等に依存するわけにはいかない。 というわけで、ここではSQLを使って採番することを考える。

CREATE TABLE tbl (num integer NOT NULL UNIQUE PRIMARY KEY)

で、どんなSQLを発行すれば次に取りたい値を求められるのか?

SELECT (num+1) FROM tbl
 WHERE (num+1) NOT IN (SELECT num FROM tbl)

これは十分直感的な書き方。num+1の値であって既存のnumの中にはない、というもの。どっかでググって見つけてきたクエリだ。ただこれはMySQLの場合dependent subqueryになる。

mysql> EXPLAIN SELECT (num+1) FROM tbl WHERE (num+1) NOT IN (SELECT num FROM tbl);
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
| id | select_type        | table | type            | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
|  1 | PRIMARY            | tbl   | index           | NULL          | PRIMARY | 4       | NULL |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | tbl   | unique_subquery | PRIMARY,num   | PRIMARY | 4       | func |    1 | Using index; Using where |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
2 rows in set (0.00 sec)

この「dependent subquery」というやつはMySQLにおいては忌み嫌われている有名な「遅くなる」クエリで、よく「外側から評価される」と称される。外側が評価されて、結果それぞれに対して内側のサブクエリが毎回実行されるという意味で直感的な動きとは異なってしまい、効率良さそうに書いたつもりが実は良くない、という問題をはらんでいるらしい。遅くなってしまうクエリにしても、内外を逆転して書ける種類のものであれば、良くなるんだろう。 ↑で上げたクエリの場合は外側と内側が同じ分量なのでまあ、そんなに悪化するとは思えない。実際いろいろ試してみたが、そんなに遅くはならない。 JOINを使ってみる場合はこんなクエリになる。

golangの欠陥

Go(golang)はかなりいろいろなことがよく考えられた良い言語だが、大きな欠陥がある。その欠陥はやはりimport。gitやmercurial等のリモートリポジトリを指定できてそれはそれで大層便利ではあるのだが、タグやブランチ、バージョン等を指定できない(常にmasterのHEADが使われる)ことがしばしば問題となる。 このimport問題の中でもとりわけ大きなものが、local importの問題だ。 リポジトリ上で開発していて、同じリポジトリのサブディレクトリを対象にimportしたいとする。

import "./subdir"

通常はこれで良いのだが、$GOPATH/src以下もgo getでcloneしてくるリポジトリになっていて、ここのソースに対して作業をしていると、相対パスによるimportがエラーになる。

can't load package: /path/to/go/src/github.com/wtnb75/xxxxx/yyyy.go:16:2: local import "./subdir" in non-local package

つまり、$GOPATHにのっとって開発する(実際golangではそれが推奨されている)場合は、こう書かなければならない。

WordPressの「スパムをすべて削除」って遅くない?

コメント画面の「スパムをすべて削除」する操作が全く戻ってこない。しびれを切らしてリロードするとちょっとは消えてるけど大部分が残ったまま。 このサイトにはほとんどコメントは来ないのだが、普通の設定(akismetで自動判定されたspamが1ヶ月で自動消去)でも常時数千件のspamが溜まっている。それでたまに全件削除しようとボタンを押してみるが…スピードが遅すぎる。何をやっているのか? MySQLのDELETEが遅いのかも、と思って試しに以下のSQLを実行してみたら、瞬時に終わった。綺麗さっぱり。まあすぐに10件を越えてくるのだが。

DELETE FROM wp_comments WHERE comment_approved='spam'

WordPressは出しているSQL文がものすごく下品なものになっているに違いないな。どうせPHPで1件1件チマチマ消しているんだろう。自動削除というのもたぶんメチャクチャなSQLを発行していて無駄に重くなっていた可能性もあるな。

ActiveRecordのincrementはポンコツか?

SQLではカウンタのincrement/decrementは1つの文で済ますことになっている。

UPDATE table SET counter = counter + 1 WHERE ...

ActiveRecordのモデルにはincrementやdecrementの定義がある。そうか便利だなぁ…??

1.9.3-p545 :021 > Tbl.take.increment!(:counter, 2)
D, [2014-09-04T00:36:32.902849 #73566] DEBUG -- :   Tbl Load (0.1ms)  SELECT "tbls".* FROM "tbls" LIMIT 1
D, [2014-09-04T00:36:32.903201 #73566] DEBUG -- :    (0.0ms)  begin transaction
D, [2014-09-04T00:36:32.904809 #73566] DEBUG -- :   SQL (0.4ms)  UPDATE "tbls" SET "counter" = ?, "updated_at" = ? WHERE "tbls"."id" = 1  [["counter", 10], ["updated_at", 2014-09-03 15:36:32 UTC]]
D, [2014-09-04T00:36:32.914149 #73566] DEBUG -- :    (9.1ms)  commit transaction
 => true

SELECTで読んで、Rubyが足し算して、UPDATEで足した結果を書き出す…こんなのってないぞ。それをORマッパーがincrementと名乗るか普通? 正解は、モデルのクラスメソッドのupdate_countersを使う。これは複数のカウンタの操作が可能。第1引数のオブジェクトIDも複数書けるし、第2引数以降のカラム名も複数書ける。オブジェクトから呼べないのが使いにくいのを除けば、普通のSQLが発行される。まあ↓の例ではIDを求めるのにSELECTは発行されるが、UPDATE自体は普通にDB側で加算されてくれる。