@ledsun blog

Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

優秀なプログラマになるために

みんな良いこと言うので、刺激を受けて考えたことを記録します。

生きてるだけで丸儲け

優秀なプログラマーになるためのコツ · GitHub

優秀なプログラマーに「育つ」んだし、それには時間が必要

優秀なプログラマーになるということは、上記の通り長時間を要するということも踏まえると、メンタルヘルスにリスクがある環境に長時間暴露されることが不可避である

業界で長きにわたり活躍し続けている人というのは、それだけですでにひとかどの人物

すごく良いです。 優秀なプログラマになる前に、死んでしまっては元も子もありません。 生き延びることはなにより大切です。

幸か不幸か現状のIT業界はハードなストレスにさらされやすい環境です。 生き延びて前線に居続けるのは優秀な証です。 間違えやすいのは「優秀なプログラマになるには、長い時間IT業界を生き延びなければいけない」因果はありません。 優秀になるための方法としては参考になりません。

人間、つらい状況にいつ巻き込まれるかわかりません。 生き延びる方法は重要です。 炎上プロジェクトでチームメンバーを失った悲しみを癒すために、自分なりの生存戦術を書きます。

ストレス対処法

ハードな環境で生き延びるには、平時にストレス対処法を身につけておきます。 単に「趣味を持つ」です。 ハードな状況で、新しいストレス対処法を試そうとしても、学習するコストがストレスになって取り入れられません。 日頃から、ほどほどに遊んでおくと良いでしょう。

適当に羅列すると

  • 運動・瞑想・睡眠
  • 散歩・旅行・ドライブ・自転車
  • 食事・飲酒
  • 映画・観劇・音楽
  • ゲーセン・麻雀・ギャンブル
  • アニメ・漫画・ゲーム・読書
  • プログラミング・プラモ・イラスト
  • キャバクラ・ホストクラブ

特に、ストレス対処法としては

  1. 自分に合っていること
  2. 金銭的、時間的に繰り返し実行可能であること
  3. 一つに頼らないこと

が大事です。

「男が痴漢になる理由」という本によれば、

痴漢の再犯防止プログラムの参加者は

ストレス・コーピングの選択肢が少ない傾向があります。

だ、そうです。(ストレス・コーピングはストレス対処法だと思ってください。)

ストレス対処法の選択肢が少ないと、一つの方法にのめり込み、過激化と依存化のリスクが高まります。 個人的な経験では、特にストレスが強い場面では、過激化する傾向があります。 気をつけましょう。

「自慢できない趣味」とか「恥ずかしい趣味」とかの概念は、生きる死ぬに比べれば大切ではありません。 法の範囲で、いろんなパターンのストレス対処法を試しておきましょう。

撤退戦術

プロジェクトが炎上した際にエモーショナルな対応をすると燃えつきやすいので危険です。

「タスク殺すマシーン」になることと、「人間に戻る儀式」を用意しましょう。

タスク殺すマシーン

人間は、状況がコントロールできずに長時間労働をすると、ストレスを強く感じます。

プロジェクトの終わりが見えない状況では、「この大きなタスクが終わればプロジェクトが終わる」と希望的な予想を立てがちです。 単に希望なので、特定のタスクを終わらしても、プロジェクトは終わりません。 このマインドセットは単発のタスクを終わらせるには有効ですが、終わりのないプロジェクトでは逆効果です。 何度も希望を折られ、コントロールできないことを実感することになります。 大きなストレスを蓄積します。

プロジェクト全体のコントロールを放棄して、もう少し小さな状況をコントロールします。 プロジェクトの終わりのことを考えるのは無視して、 直近で実行可能なタスクを定義し、タスクを殺して常に進捗を出し続けます。 状況を逆転するような大きなタスクは定義しません。 絶対に実施可能なタスクを定義します。 不可能なタスクを押し付けられた時は、実行可能なタスクもねじ込みます。

少しずつでも進んでいる印象を自分に与えます。 ちょっと良いことがあると、プロジェクトの終わりのことを考えてしまいます。が、考えるのはやめましょう。 未来のことは、せいぜい次の休暇のことを考えて、死なないように体調コントロールすることを考えましょう。

すごく頑張ろうが、淡々と頑張ろうが、プロジェクトの終了まで死にさえしなければ、プロジェクト方が先に終わります。 何らかの原因で必ず終わります。 無限に投資できる組織は存在しないので、いつかプロジェクトに投入可能な資金は尽きます。 お金がなければプロジェクトは続けられません。終わります。

プロジェクトの失敗や成功より、生存が第一目標です。

人間に戻る儀式

マシーンモードはストレスへの反応を軽減するには便利ですが、 感情を殺しすぎるとストレスを検知できなくなります。 自分でわからないうちに、突然死んでしまうので危険です。

毎日でなくても週に2,3回は、「人間に戻る儀式」を実行して感情を復元しましょう。 その後ストレスに応じて、ストレス対処法を実施しましょう。 経験上、マシーンモード中にストレス対処法を実行しても効果が少ないように思います。

僕の場合は、ある時期の「人間に戻る儀式」が、帰宅前の一人飲みでした。出費が大変でした。 胃へのダメージがバカにならないので、ハイボール等の薄めのお酒を量を控えて飲む、みたいなスタイルに変わりました。 それ以前は、ウイスキー等の強いお酒も飲んでいました。

また、マシーンモードでは他人に対する優しさを失います。 家庭持ちの人は気をつけましょう。 職場の人間関係は、生きる死ぬよりは低優先です。

どんな方法を講じても、つらい環境が長期間続くと、人間は耐えられません。 死ぬ前に、逃げる必要があります。

逃げる選択をするのは勇気と体力が要ります。 つらい状況では体力は削られているので、決断し行動に移すことは、多くの場合無理でしょう。

結局は、自分のキャパシティを超える辛いプロジェクトに遭遇しないに運につきます。 願わくば辛いだけの戦いはしたくないものです。

技術力を身につける方法

車輪を再発明する

watilde.hatenablog.com

車輪を再構築する。多くの人は、それを止めてくるが、止めてくる人は再構築をしたことがないことが多い。彼らは、おそらく車輪について理解していない、かもしれない。きっと、新しい車輪が必要な際に新しいトラックを買うタイプの人。

素晴らしいです。 お仕事で使うライブラリやフレームワークは既存のものを使うと良いと思います。 ドキュメントが多いため、自分以外の開発メンバーの学習コストが低く、メンテナンスも(あるいは)外部に依存できます。 何より枯れたインタフェースは、設計コストの節約になります。

自分の技術力を高めるためには別です。 車輪の再発明をして、フレームワークやライブラリを実装しましょう。 全部を実装しなくても、一部だけでも実装してみましょう。 実装したことがあれば、既存のフレームワークを見たときに、フレームワーク全部が必要なのか、どこか一部分が必要なのか判断できるようになります。 どうしてそういう実装や構成にしたのか、作者の気持ちがわかります。 フレームワークのどの部分が、欲しい機能に関係するか目星がつきます。 機能が足りなくても、なぜ他の機能を重視しているのかわかります。 モジュール構成や依存関係も目星がつくので、ソースコードを読んで必要な部分だけをパクることもできます。 これらの判断できなければ、既存のフレームワークをそのまま使うしかありません。 ゴールデンハンマー病です。

脱ゴールデンハンマー病

If all you have is a hammer, everything looks like a nail. (ハンマーを持つ人にはすべてが釘に見える)

アブラハム・ハロルド・マズロー (Abraham Harold Maslow, 1908–1970)のお言葉です。

以前は「ハンマーだけでなくドライバーも身につけて、適材適所で使えばいいんでしょ?」と思っていました。 この方法は、思ったより応用が効きません。

今あるハンマーで打てないぐらい大きな釘を打たなければいけない時に困ります。 ハンマーに何かくっつけて大きくすれば良いかもしれません。 何をつけてどうやって固定すれば、大きな釘を安定して打てるようになるでしょうか? 改造するのは中々大変です。 ハンマーの作り方を知っていれば、大きなヘッドを買ってきて付け替えるだけで済みます。 解決方法の探し方に幅だけでなく深さが出てきます。 ゴールデンハンマー病から抜け出ることができます。

フレームワークやライブラリの場合も、フレームワークAとBを比較してより良い方を選ぶ、以外の選択肢がでてきます。 既存のフレームワークやライブラリは色々な用途に汎用的に使えるように作られています。 多くの場合ではオーバースペックです。 フレームワークで解決できる問題もありますが、フレームワークを使わない方が工数が減ることもあります。

学習の助

車輪の再発明をしようにも、徒手空拳で取り掛かるのは大変です。 世の中には、車輪の再発明を助けてくれる教材があります。

UNIXプログラミングから始まって、簡易HTTPサーバの実装に至ります。 HTTPサーバの実装はまさに車輪の再発明です。

著者の用意したお助けライブラリを使って、簡易Rubyインタプリタが実装できます。 Rubyインタプリタの実装も車輪の再発明です。

とちぎRuby会議07でラムダノート株式会社の鹿野さんが 「知識のテコになる本を作りたい」(正確な内容は覚えていません)的なことを言っていたのは、なるほど、こういうことなのかな?と今になって思いました。

優秀なプログラマとは?

ここまで書いておいて、どういう人が優秀なプログラマかの定義がないので、 一応、整理しておきます。 評価軸の一つだと思ってください。

すごい専門家さんとすごい素人になるためには – Koichiro Honda – Medium

優秀な通訳の方はどういう人かといわれたら、それは語学ではなくて、例えばめちゃくちゃビジネスが詳しい人である。パフォーマンスを決めるのは、話すコンテンツの方をどれだけ理解しているか、にかかっている。英語を喋れるから発注するわけじゃない

仕事を頼む側からするとと、仕事のコンテキストを理解してくれる、むしろ先読み・深読みしてくれるのはとてもありがたいです。 「SEには、実装技術も大事だけど業務知識も大事だよ」みたいな話です。

これは「車輪の再発明」とはちょっと違う話です。 どちらかというと「ハンマーだけでなくドライバーも身につけよう」な話です。 優秀なプログラマになる方法は一つではありません。

DBエンジニアだけどアプリケーションコードも多少書ける人や、デザイナーだけどJavaScriptもいくらか書ける人たちは、すごくありがたいです。 プログラミングでも「組み込みが得意だけどネットワークもわかる」や、「フロントエンドが得意だけどサーバサイドも多少わかる」は大事です。

多くの現実の問題は、特定の領域にあるわけではなく、領域と領域の間にあります。 どっちの領域の問題として扱うのが良いのか判断できることは、問題解決の役に立ちます。

アプリケーション開発の文脈で言えば、見た目に関わる機能、たとえば「表示内容の絞り込み」をサーバーサイドで実装するか、フロントサイドで実装するか迷うことがあります。この時に相談できるプログラマはとても有用です。

一つの領域しか知らないと、その領域で解決しようとしがちです。 その領域でやるのがもっとも効率的とは限りません。 自分の一番得意な領域以外のプログラムも素振りして、雰囲気を掴んでおくことは大事です。 そういう時はQiitaによくある「HelloWorldやってみた」な記事が結構役に立ちます*1。 嫌いな人もいるみたいですが、僕は好きです。

おまけ

自分用の簡単な道具を作るのもオススメです。 コツは自分だけのために作ることです。 他の人にも使えるようにと考えるとグッと難易度が上がります。 今まで作ったものを自慢します。

github.com

毎週金曜日に掃除の時間をチャットワークに書き込んでくれます。

github.com

面談の組み合わせをモンテカルロ法で決めてくれます。

github.com

cpxコマンドでhtmlファイルをコピーする時に、srcのリンクをhttpsに書き換えてくれます。 gulpとかのそれっぽい変換ツールをnpmスクリプトに置き換えるために使った気がします。

github.com

Markdownで書いたテスト手順の順序を入れ替えます。

github.com

gitlabのWebhook通知をチャットワークに書き込んでくれます。 いまはgitlab使っていないので、使っていません。

github.com 「技術ブログ書いて」と通知するbotです。 効果なかったので止めました。

github.com ESLintの適用できそうなルールを列挙します。 ESLintのルールが増えた時に使いました。 適用できそうなルールを機械的に適用していくと、ルールセットに思想がなくて、意外と嬉しくなかったです。 チェックして欲しい部分にあったルールを探して手で入れた方が、いい感じのルールセットになります。

github.com とあるフレームワークを使っている時に、gitの差分から変更のありそうなユーザ操作のヒントを洗い出してくれるツール。 テスト項目の優先順位を考えために使いました。

github.com

ブログの記事にアフィリエイトリンクを埋めるためのコマンド。

*1:情報が古いことがあるので、本家情報も確認する必要はあります

Node.jsでつくるNode.js その2

ledsun.hatenablog.com

の続きです。四則演算の対応するオペレーター(演算子)を増やします。

オペレーターを増やす

前回+に対応しました。 次に、-, *,/,%に対応します。

実装

switch文に演算子ごとの分岐を追加するだけです。

const esprima = require('esprima')
const util = require('util')

console.assert(test('1 + 1') === 2)
console.assert(test('1 + 2') === 3)
console.assert(test('1 - 2') === -1)
console.assert(test('2 * 2') === 4)
console.assert(test('10 / 2') === 5)
console.assert(test('100 % 49') === 2)

function test(expresssion) {
  const parsed = esprima.parse(expresssion)

  console.log(util.inspect(parsed, false, null))

  const body = parsed.body
  for (const statement of body) {
    return evaluate(statement)
  }
}

function evaluate(statement) {
  switch (statement.type) {
    case 'ExpressionStatement':
      switch (statement.expression.type) {
        case 'BinaryExpression':
          let left, right
          switch (statement.expression.operator) {
            case '+':
              [left, right] = getOperandFromBinaryExpression(statement.expression)
              return left + right
              break;
            case '-':
              [left, right] = getOperandFromBinaryExpression(statement.expression)
              return left - right
              break;
            case '*':
              [left, right] = getOperandFromBinaryExpression(statement.expression)
              return left * right
              break;
            case '/':
              [left, right] = getOperandFromBinaryExpression(statement.expression)
              return left / right
              break;
            case '%':
              [left, right] = getOperandFromBinaryExpression(statement.expression)
              return left % right
              break;
            default:
              console.log(`unknown operator ${statement.expression.operator}`);
          }
          break;
        default:
          console.log(`unknown expression ${statement.expression}`);
      }
      break;
    default:
      console.log(`unknown type ${statement.type}`);
  }
}

function getOperandFromBinaryExpression(expression) {
  let left;
  if (expression.left.type === 'Literal') {
    left = expression.left.value
  } else {
    console.log(`unknown type ${expression.left.type}`);
  }

  let right;
  if (expression.right.type === 'Literal') {
    right = expression.right.value
  } else {
    console.log(`unknown type ${expression.right.type}`);
  }

  return [left, right]
}

leftとrightの値をとる処理をgetOperandFromBinaryExpression関数にしました。

項数を増やす

1 + 1 + 1のように式の項数を増やします。

この時、ASTは

Script {
  type: 'Program',
  body:
   [ ExpressionStatement {
       type: 'ExpressionStatement',
       expression:
        BinaryExpression {
          type: 'BinaryExpression',
          operator: '+',
          left:
           BinaryExpression {
             type: 'BinaryExpression',
             operator: '+',
             left: Literal { type: 'Literal', value: 1, raw: '1' },
             right: Literal { type: 'Literal', value: 1, raw: '1' } },
          right: Literal { type: 'Literal', value: 1, raw: '1' } } } ],
  sourceType: 'script' }

leftの中にBinaryExpressionが入れ子になっています。

これに対応すると、小町算を計算できるようになります。

(1 + 2) / 3 * 4 * (56 / 7 + 8 + 9) = 100

実装

BinaryExpressionの評価を再帰的にしたいので、evaluateBinaryExpression関数を作って再起呼び出しします。

const esprima = require('esprima')
const util = require('util')

console.assert(test('1 + 1') === 2)
console.assert(test('1 + 2') === 3)
console.assert(test('1 - 2') === -1)
console.assert(test('2 * 2') === 4)
console.assert(test('10 / 2') === 5)
console.assert(test('100 % 49') === 2)
console.assert(test('1 + 1 + 1') === 3)
console.assert(test('(1 + 2) / 3 * 4 * (56 / 7 + 8 + 9)') === 100)

function test(expresssion) {
  const parsed = esprima.parse(expresssion)

  console.log(util.inspect(parsed, false, null))

  const body = parsed.body
  for (const statement of body) {
    return evaluateStatement(statement)
  }
}

function evaluateStatement(statement) {
  switch (statement.type) {
    case 'ExpressionStatement':
      switch (statement.expression.type) {
        case 'BinaryExpression':
          return evaluateBinaryExpression(statement.expression)
          break;
        default:
          console.log(`unknown expression ${statement.expression}`);
      }
      break;
    default:
      console.log(`unknown type ${statement.type}`);
  }
}

function evaluateBinaryExpression(expression) {
  let left, right
  switch (expression.operator) {
    case '+':
      [left, right] = getOperandFromBinaryExpression(expression)
      return left + right
      break;
    case '-':
      [left, right] = getOperandFromBinaryExpression(expression)
      return left - right
      break;
    case '*':
      [left, right] = getOperandFromBinaryExpression(expression)
      return left * right
      break;
    case '/':
      [left, right] = getOperandFromBinaryExpression(expression)
      return left / right
      break;
    case '%':
      [left, right] = getOperandFromBinaryExpression(expression)
      return left % right
      break;
    default:
      console.log(`unknown operator ${expression.operator}`);
  }
}

function getOperandFromBinaryExpression(expression) {
  return [getOperandValue(expression.left), getOperandValue(expression.right)]
}

function getOperandValue(operand) {
  switch (operand.type) {
    case 'Literal':
      return operand.value
    case 'BinaryExpression':
      return evaluateBinaryExpression(operand)
    default:
      console.log(`unknown type ${operand.type}`);
  }
}

getOperandValue関数はleftとrightにコピペするのが面倒だったので、関数にしました。

RubyでつくるRubyとの違い

EsprimaのASTはstatementとexpressionの二階層になっています。 一方minirubyのASTたexpressionだけの一階層です。

Rubyで作るRubyソースコードは本を買って確認してください。

式と文の取り扱い

これは言語仕様の違いによるものです。

Ruby

プログラムは式を並べたものです

プログラム・文・式 (Ruby 2.4.0)

式と文に区別はありません。

JavaScriptでは式と文は区別されます。 例えば

1 + 1

は式です。

var i = 1

は文です。JavaScriptの文は値を返しません。