プログラマ35歳定年説に物申さない

Filed in プログラム

プログラマ35歳定年説というものがある。
35歳になると能力が衰えて技術習得が追い付かず時代の流れについていけなくなるというものである。

これについては常々違和感を感じていた。
名目上は頭脳労働であるプログラマが35歳程度で働けなくなるなんてあるのだろうかと。
しかし、自分自身がその年齢になってみると確かにいろいろ厳しいと感じることが多くなってきた。

私自身が感じていることをいくつか述べてみたいと思う。

  1. 糞な設計に強烈な違和感を感じるようになった。
  2. 割り込み作業が増えた。
  3. コードを書いてもなぜかあまり評価されない。

それぞれについて少し説明をしたいと思う。

1.糞な設計に強烈な違和感を感じるようになった。
いろいろな経験や勉強を経て、自分自身が最も良いと思っている設計思想が確立されたと思っている。
そのためかダメな設計に対して違和感を感じるようになってきた。

私自信が設計で気をつけることはいろいろあるものの、最も気を配っているのは適切なスコープである。適切なスコープを切るためには適切なクラス設計、コンポーネント設計、アーキテクト設計が必要という比較的上流のレベル、利用する付近で変数宣言をする、関連処理は一箇所に纏めるひいてはメソッドとして切りだせるという下流レベルまで一通り適用できる。

しかしこれに反するものを見たときになんとなく拒否反応が出るようになってきてしまった。こんなことではいけないと思いつつもどうもだめだ。

メソッドの先頭で10も20も変数が定義されているのに、メソッドの中身は実質いくつものブロックに分かれており、それぞれのブロックでは数個の変数しか使わないというコードは比較的よく見かける。
こんなものは変数定義を利用箇所の直前に変えメソッドを分割するべきである。20も変数を覚えつつメソッドの処理を追うなんて人間の脳の処理能力を超えているのである。

昔は頑張って読み解いていたが歳を取るごとにバカバカしさの方を強く感じるようになってきてしまっている。立場的にも指導する立場になり批判的にコードを見ているということが少なからず影響しているのかもしれない。

2.割り込み作業が増えた。
コード書きとは関係のない割り込み作業が非常に増えたと感じている。年齢というより立場の問題ではあるがそのような立場になる時期と年齢は無関係ではないだろう。なにがしの状況はどうなっているのか報告書を書けだの、進捗はどうなのか数字を纏めろ、突如発生した問題の対策会議を開くから集まれだといった作業が10年前に比べてやけに増えた。

正直、現場のプログラマへの配慮が足りないと思う。1時間を6回与えられた6時間と6時間を1回与えられた6時間はプログラマにとって意味が違う。どういうわけかそういうところに配慮してくれる人は少ない。

個人的な意見では作業が中断されるとだいたい30分から1時間くらいの作業内容は失われるくらいに考えて貰いたいのである。なるべく効率よく再開できるように手は打つが効率が悪くなることは避けられない。

3.コードを書いてもなぜかあまり評価されない。
これは開発手法やプロジェクトにも依存する話だと思う。ただ、私が関わったプロジェクトでは管理層がコードを見れない、見ないことが多かった。なので良いコードを書いているか悪いコードを書いているか判断しようがないというのはあったのかもしれない。正直これは良くないと思う。管理者もコードを書けとまでは言わないが見るくらいはすべきだと思う。そうでなくてもサブリーダーなどに誰が綺麗なコードを書いているかくらいは確認すべきだと思う。

比較的先進的な企業ではマネージャ層もある程度積極的にコードを書くそうだがさもありなんという感じである。なぜならコードの善し悪しはお金に直結する。綺麗なコードにはバグが入りにくい。少なくともバグが見つけやすい。修正もしやすい。拡張もしやすい。初見でも理解しやすく手が空いている人を必要な時だけ連れてきて保守させることができるため専属の保守員というのも不要になる。非常に多くのメリットがある。

だがこれらはあまり目に見えるものではない。いや、目には見えているはずである。だが何故そうなのかが考えられて評価に結び付くことが少ない。評価されるべきメンバー、あるいはふざけるなというコードを書いたメンバーはその時にはすでにプロジェクトから外れているということも多い。
そういったこともあり、バグ数の多寡は評価されてもコードの善し悪しが評価されることは少ない。そのせいなのか良いコードを書く人もそうでない人もひとくくりにされてしまってコードを書く行為への評価が高くなく、コードを書くのは給料の低い人となっている感がある。そのため給料が上がってくる=>コードを書かなくなってくる=>書けなくなるということがあるように思われる。

私の経験上、汚いコードを書く人が多いプロジェクトは属人的になっていることが多く、人員の入れ替えが難しい。新たな人を投入してもコードの理解が難しいため戦力になるまでに長い時間を要すが、管理者はそれが分かっていないから人を増やした分だけ進捗が上がると思っている。その分を古参のメンバーが頑張ってカバーしようとするが、時間が足りないため小手先のコードが増えてますますコードが汚くなっていくという悪循環となる。こうなるともうおしまいだ。

繰り返し言うがコードの善し悪しはお金に直結する。高給取りがコードを書いていても何もおかしいことは無いはずだ。彼・彼女の書いたコードは綺麗なのでお金がかからなくていいね~という会話がされるようなプロジェクトに参加したいものである。

さて、いろいろ書いてきたがそういうわけで年齢によりある種の壁にぶつかるということはあるのかもしれないと考えるようになってきた。

だがまあ仕事でコードを書くことは減ったとしても、技術的に付いていけるかどうかということについては最終的には個人の意識の問題だ。仕事はどうあれ生涯プログラマであると言えるように常に技術だけは磨いていきたいと思っている。

GitLab+Jenkinsで超ハマった

Filed in コンピュータ

自宅にGitLabとJenkinsを構築したんですが、超ハマりました。
ググってもなかなかそのものズバリの解が見つからずかなり悩んだので
同じ問題で躓いた誰かの一助になれば幸いだと思いここに記録をのこしておきます。

GitLabのバージョンは6.4.2です。Gitoliteは使わずにGitLab Shellの1.7.9を使っています。
jenkinsのバージョンは今回あまり重要ではなかったので適当に。
構築の手順などはググれば出てくるので省略しますがGitLabとjenkinsを同じサーバにインストールしています。
一通り構築が終わってプロジェクトをGitLabに登録してクライアントからソースのコミットやプッシュができるのを確認し終えてjenkinsでビルドをしようとしたとこから。

秘密鍵を使ってSSHによる接続をするための設定をしました。
伏字ですけどこんな感じ。
jenkins

GitLabには公開鍵を登録してあります。

ここまでやってJenkinsでビルドを実行するとこんなエラーが。

Fetching changes from the remote Git repository
Fetching upstream changes from xxx@サーバ:ユーザ/プロジェクト.git
using GIT_SSH to set credentials 
FATAL: Failed to fetch from xxx@サーバ:ユーザ/プロジェクト.git
hudson.plugins.git.GitException: Failed to fetch from xxx@サーバ:ユーザ/プロジェクト.git
	at hudson.plugins.git.GitSCM.fetchFrom(GitSCM.java:623)
	at hudson.plugins.git.GitSCM.retrieveChanges(GitSCM.java:855)
	at hudson.plugins.git.GitSCM.checkout(GitSCM.java:880)
	at hudson.model.AbstractProject.checkout(AbstractProject.java:1411)
	at hudson.model.AbstractBuild$AbstractBuildExecution.defaultCheckout(AbstractBuild.java:651)
	at jenkins.scm.SCMCheckoutStrategy.checkout(SCMCheckoutStrategy.java:88)
	at hudson.model.AbstractBuild$AbstractBuildExecution.run(AbstractBuild.java:560)
	at hudson.model.Run.execute(Run.java:1670)
	at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:46)
	at hudson.model.ResourceController.execute(ResourceController.java:88)
	at hudson.model.Executor.run(Executor.java:231)
Caused by: hudson.plugins.git.GitException: Command "git fetch --tags --progress 
xxx@サーバ:ユーザ/プロジェクト.git +refs/heads/*:refs/remotes/origin/*" 
returned status code 128:
stdout: 
stderr: Access denied.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

それで19行目に出ているステータスコード128でいろいろ調べてみたんだが、
これが原因がいろいろありすぎてなかなか分からない。

SSHKeyを作りなおして再登録したり、ユーザのアクセス権を見直したり、restoreconを打ってみたりとうんうん数時間あれやこれやと試行錯誤。大体においてこういうときは何をやっても上手くいかないもんで一向に解決しないのでちょっとゲームでもして気分転換をしたところはっとひらめいた。

結論としてはgitlab-shellの設定がダメだった。

# GitLab user. git by default
user: git

# Url to gitlab instance. Used for api calls. Should end with a slash.
gitlab_url: "http://127.0.0.1:80/"
# GitLab user. git by default
user: git

# Url to gitlab instance. Used for api calls. Should end with a slash.
gitlab_url: "http://127.0.0.1:8080/"

何のことはない。リバースプロキシから8080番に飛ばすためにポート番号を変えていたのにGitLab Shellの対応をしてなかったということでした。別PCをクライアントにしてしか使ってなかったのでなかなか気づけませんでした。

しかしもうちょっとエラーメッセージが分かり易くできそうなものですけどね。

デジタルの脳を持つ人々

Filed in プログラム

1つ目のジョブが成功なら2つ目のジョブを実行するという処理を
何の疑問も持たずにこんなコードで書く人達がいる。

public enum Result {
	SUCCESS,
	FAILED
}

public class TaskExecuter {
  public void execute(Job job) {
     job.firstTask();

     if( job.getResult() != Result.FAILED ) {
        job.secondTask();
     }
  }
}

これはダメなコードです。さて、何がダメでしょうか。

エンハンスによりEnumがこうなったらどうでしょう。

public enum Result {
	SUCCESS,
	FAILED,
	SUSPEND
}

そう、もう期待通りには動きません。
前の処理が終わっていないのに次の処理を実行してしまいます。
なぜこんなコードを書いてしまうのでしょうか。

それは「失敗でなければ成功」という思い込みですね。
失敗でなければ成功などとは考えてはいけません。
「失敗でなければ失敗していない」以上の何物でもないのです。

私の経験上、思い込みというのはバグの発生源としてかなりの勢力を持っています。
また、バグが見つけられない理由としても一大勢力を築いています。

そんなわけでこう書くべきなのです。

if( job.getResult() == Result.SUCCESS ) {
   job.nextTask();
}

成功したとき以外には動いてもらっては困るのですから成功したら実行と素直に書けばよろしいのです。

同じようにこれはダメです。

if( job.getResult != Result.SUCCESS ) {
  throw new SomeException("errorだよ");
}

答えを曖昧にする日本人なのになぜかプログラムでは中間状態を無視してしまうような思考をするのは不思議ですね。

続・見ろ!!コードがゴミのようだ!!

Filed in ゴミコード, プログラム

せっかくなので先日書いたゴミサンプルをC++で書き直してみた。

#include <iostream>
#include <string>

class SampleClass {
public:
	int i;
	std::string s;
};

class Test {
	public:
		SampleClass* methodA(SampleClass* a, SampleClass* b);
};

int main(int argc, char* argv[])
{
	SampleClass a = SampleClass();
	a.i = 1;
	a.s = "a";

	SampleClass b = SampleClass();
	b.i = 2;
	b.s = "b";

	Test t = Test();
	
	std::cout << "== before ==" << std::endl;
	std::cout << "a= " << a.i << ":" << a.s << std::endl;
	std::cout << "b= " << b.i << ":" << b.s << std::endl;
	
	SampleClass* c = t.methodA(&a, &b);

	std::cout << "== after ==" << std::endl;
	std::cout << "a= " << a.i << ":" << a.s << std::endl;
	std::cout << "b= " << b.i << ":" << b.s << std::endl;
	std::cout << "c= " << c->i << ":" << c->s << std::endl;
	
	delete(c);
	return 0;
}

SampleClass* Test::methodA(SampleClass *a, SampleClass *b) {
	a->i = 11;
	a->s = "A";
	
	SampleClass b2 = SampleClass();
	b2.i = 12;
	b2.s = "B";
	b = &b2;

	SampleClass* c = new SampleClass();
	*c = b2;
	return c;
};

同じように作ったのだから結果は当然同じ。
ただ、戻り値cはnewしたものなので最後にdeleteしている。
この辺はJavaではそんなに意識しないことだろう。

== before ==
a= 1:a
b= 2:b
== after ==
a= 11:A
b= 2:b
c= 12:B

しかし超久しぶりにC++を書いたのでアクセス修飾子ってどうするんだっけ?と迷ってしまった。。
昔は当然と思っていたのに宣言と定義を分けるというのもかなり煩わしく感じた。
“System.out.println”と”std::cout <<" を比較すると"std::cout <<"などはプログラムを知らない人には謎の暗号にしか思えまい。。 久しぶりにC++のコードを書いて思ったのはポインタを使いたいとは思わないが、やはり概念は知っておくべきということ。 Javaで参照がどうとか勉強するよりもC++やCでポインタを学んだ方が近道な気がしている。 Javaにはガベッジコレクタがあると言ってもメモリの無駄な消費はメモリの断片化が起こってガベッジコレクタの頻発などパフォーマンスに悪影響です。インスタンスの無駄な生成自体もコストがかかりますしね。やはりJavaであってもいざというときはメモリを意識したプログラムを書くべきです。 こんなコード書いてもいいこと何もないですよ。 [java] SomeClass a = new SomeClass(); SomeClass b = new SomeClass(); // このインスタンスが作られてすぐ捨てられる b = a; [/java]

見ろ!!コードがゴミのようだ!!

Filed in ゴミコード, プログラム

まずは以下のjavaのコードをみてもらおう。

class SampleClass {
  public int i;
  public String s;
}

public class Test {
  static public void main(String[] args) {
    Test test = new Test();


    SampleClass a = new SampleClass();
    a.i = 1;
    a.s = "a";
    
    SampleClass b = new SampleClass();
    b.i = 2;
    b.s = "b";

    System.out.println("== before ===");
    System.out.println("a= " + a.i + ":" + a.s);
    System.out.println("b= " + b.i + ":" + b.s);

    SampleClass c = test.antiPattern(a,b);

    System.out.println("== after ===");
    System.out.println("a= " + a.i + ":" + a.s);
    System.out.println("b= " + b.i + ":" + b.s);
    System.out.println("c= " + c.i + ":" + c.s);
  }

  public SampleClass antiPattern(SampleClass a, SampleClass b) {
    a.i = 11;
    a.s = "A";

    SampleClass b2 = new SampleClass();
    b2.i = 12;
    b2.s = "B";
    b = b2;

    SampleClass c = new SampleClass();
    c = b2;
    return c;
  }
}

mainメソッドでantiPatternメソッドを呼ぶ前後で変数値を出力している訳だが何が出力されるか分かるだろうか。

結果はこうである。

== before ===
a= 1:a
b= 2:b
== after ===
a= 11:A
b= 2:b
c= 12:B

変数cは良いとしても、aやbまでもmainメソッドだけを見ていては決して分かりようがない結果が出力されるのである。さらに言うとantiPatternメソッドを見てみると直感的にはbも更新されてよさそうな気がするのだが実際はそうはならない。この辺はjavaのメソッドはポインタの値渡しであるという言語仕様によるものである。

しかしこれは言語仕様が許しているからと言って書いてはいけないコードの代表格と言えるだろう。mainメソッドを読んでいるのにantiPatternメソッドがどうなっているかまで意識しなくてはいけない。

上の例ではantiPatternメソッドが実行ステップにして10行足らずで完結しているのでまだよいが、antiPatternメソッドも同じように別のメソッドAを呼びだしてAの中でも渡した変数の中身が変わって返ってくるとなったらどうだろうか。さらにAの中でもBを呼び出し、Bの中でもCを呼び出し・・・。
結局全部のコードを理解しないと高々数十行のmainメソッドすら読み解くことができない。
これでは保守性が悪くてどうしようもない。
これをゴミコードと呼ばずにいられるだろうか。いや、いられまい(反語)。

さて、突然こんなことを書いたのは仕事でこんなコードを見たからである。
実際はもっと処理があるが、ポイントだけ抜粋した。

public class Y {
  public void methodA() {
    ・・・
    XListGenerator generator = new XListGenerator(a,b,c);
    List<X> xList = new ArrayList<X>();
    ・・・
    List<Something> somethingList = new ArrayList<Something>();
  ・・・
    generator.set(xList, somethingList);
  }
}

クラス名が悪い、変数名が悪いとか言いたいわけではない。
methodAが何をやっているか分かるだろうか。
「generatorに2つのlistを設定している。」と読みとるのが自然ではないだろうか。

しかしgenerator.set(list,somethingList)の中を見て愕然とした。以下のような内容なのであった。

   public set(List<X> xList, List<Something> somethingList) {
       for(i=0; i<somethingList.size(); i++) {
          String s= somethingList.get(i).getString();
          xList.get(i).setS(s);
       }
   }

なんなんでしょうかこれ。

完全に依存関係があるんだしメソッドを分けたって百害あって一利なしだ。setの主体もgeneratorなのかなんなのかよく分からない。
これでいいじゃない。

public class Y {
  public void methodA() {
    ・・・
    List<X> xList = new ArrayList<X>();
    ・・・
    List<Something> somethingList = new ArrayList<Something>();
  ・・・
    for(i=0; i<somethingList.size(); i++) {
       String s= somethingList.get(i).getString();
       xList.get(i).setS(s);
    }
  }
}

どうしてもgeneratorを使いたいなら、なるべく原型を残したとしてこうだ。

public class Y {
  public void methodA() {
    ・・・
  XListGenerator generator = new XListGenerator(a,b,c);
    ・・・
    List<Something> somethingList = new ArrayList<Something>();
  ・・・
    List<X> xList = generator.generate(somethingList);
  }
}

プログラムは動けばよい!というのは自分一人で作っている場合に限るのである。
こんなコードを読まなければいけない他人の身にもなってコードは書くべきである。