マイペースなRailsおじさん

Ruby、Ruby on Rails、オブジェクト指向設計を主なテーマとして扱います。だんだん大きくなっていくRuby on Rails製プロダクトのメンテナンス性を損なわない方法を考えたり考えなかったりしている人のブログです。

Socket.tcp_server_loopでかんたんHTTP

ちゃんとしたHTTPサーバーを作るのって難しいんですが、それっぽいものを作るなら実はそんなに難しく無いです。

HTTPサーバーの処理の流れ

とってもざっくり説明すると、HTTPサーバーの処理はこんな流れです。

  1. TCPコネクションを確立する
  2. HTTPリクエストを受信する
  3. HTTPレスポンスを送信する
  4. TCPコネションを切断する

「1. TCPコネクションを確立する」でまとめてしまっていますが、この中にもいくつか手順があります。TCPソケットをbindしてlistenしてacceptします。混乱したでしょう。そうだと思います。

  1. TCPソケットを作成する
    コンピュータがデータを送受信するとき、何らかの通信方式に従っている必要があります。英語しかしゃべれない人が訪ねてくる施設の受付に日本語しかしゃべれない人を置いたらまずいのと同じです。何らかの通信方式でデータを送受信するための出入り口がソケットです。
    ソケットにはいくつか種類がありますが、HTTPはTCPという通信方式で接続してくるため、TCPソケットを使います。
    ※) RFCではHTTPは特定のトランスポートプロトコルに依存しない、としている。が、基本的にはTCPと考えて良い。RFC 7230 — HTTP/1.1: Message Syntax and Routing (日本語訳)
  2. bindする TCPソケットが、コンピュータの持つポートのうちどこに繋げるかを支持します。普通、HTTPサーバーはTCPの80番ポートに接続します。
  3. listenする ソケットを接続待ちの状態にします。これで外部から接続できるようになります。
  4. acceptする 接続されるまでプログラムの実行を停止(ブロック)します。接続されたら、接続してきたクライアントと通信可能な状態にして、プログラムを再開します。

大まかにこんな流れでTCPコネクションを確立します。大変です。C言語で実装しようものなら、謎の構造体や識別子をいっぱい覚えないといけないので非常に大変です。

Socket.tcp_server_loop

TCPコネクションの確率が大変なのはわかっていただけたと思います。ですが安心してください、Rubyには、「1. TCPコネクションを確立する」を代わりにやってくれるメソッドがあります。スゴーイ。

docs.ruby-lang.org

これを使うと、TCPコネクションを確立して、Socket.tcp_server_loopに渡したブロックを処理してくれます。

コネクションを確立したら、socketからメッセージを読み取り、加工して相手に送信するプログラムはこのようにかけます。9999は接続を待ち受けるTCPのポート番号です。

require 'socket'

Socket.tcp_server_loop(9999) do |sock|
  msg = sock.recv 1000
  sock.sendmsg "#{msg.strip}を受信しました"
ensure
  sock.close
end

これは、ncを使うと動作確認できます。

$ echo hello |  nc localhost 9999
helloを受信しました

注意点としては、このプログラムでは、複数のリクエストを同時に扱うことができない点があります。マルチスレッディングやforkを利用すると、この問題に対処できますがここでは扱いません

HTTPサーバーもどき

TCPサーバーが簡単に作れることがわかったので、HTTPサーバーもどきを作ります。

メッセージフォーマット

HTTP1.1のレスポンスは、ざっくりこのようなフォーマットに従っていることを期待します

ステータスライン
レスポンスヘッダー
CRLF(改行)
メッセージ本体

レスポンスヘッダーは省略可能です。 ステータスラインはHTTPのバージョンとHTTPステータスの組み合わせです。 メッセージ本体には、HTMLなど、なんでもいいのでデータが入ります。

つまり、最低限こんな風になっていればHTTPリクエストとしては正しいです。

HTTP1.1 200 OK

こんにちは

これを踏まえて、HTMLを返すようなプログラムはこんなかんじにかけます

Socket.tcp_server_loop(9999) do |sock, _client_addrinfo|
  sock.write <<~RESP
    HTTP/1.1 200 OK

    <html>
    <body>
    <h1>こんにちは 世界</h1>
    <p>HTTPを使って通信するサーバーを作ったよ。</p>
    </body>
    </html>
  RESP
ensure
  sock.close
end

え?ほんとに動くの?という感じですね。 ブラウザでhttp://localhost:9999/を開きます。

f:id:ytnk531:20201020084516p:plain

うごいてます。HTTP自体はそんなに難しくない気がしてきましたね。

おわりに

以上、HTTPのサーバーもどきは意外と簡単に作れるという話でした。

注意ですが、今回作ったプログラムは、クライアントの要求を無視してただただ単純なリクエストを返すだけのものなので、まだまだサーバーと呼べる代物ではありません。 少なくとも、リクエストメッセージのパース、要求されたファイルの返却、GET、POST、HEADなどのHTTPメソッドへの対応、複数リクエストの同時処理…などなどやらなければいけないことはたくさんあります。 ただ、鬼門となりがちなTCPのコネクション確立が、ほとんどなにも知らなくてもできるっていうのは結構すごいことなんじゃないでしょうか。Rubyでネットワークプログラミングってけっこう楽しいかもしれません。

extendを使ったStrategyパターン風のアルゴリズム切り替え

に、extendの興味深い利用方法が出てきていたのでご紹介。この本の、8.13 タイプコードからモジュールのextendへ(Replace Type Code with Module Extention) の内容をStrategyパターン と比較します。

やりたいこと

レンタルビデオの料金計算を行うプログラムを考えます。料金の計算は、下記のルールで行います。

  • レンタル料は、ビデオの基本レンタル料に利用者種別ごとの割引を適用して計算する。
  • ビデオごとに値段が設定してあり、これを基本レンタル料と呼ぶ。
  • 利用者種別には、子供と大人の二種類がある。
    • 大人: 基本レンタル料から100円を引いた金額がレンタル料となる
    • 子供: 基本レンタル料の半額がレンタル料となる

素直にcase文

case文を使って、計算方法を切り替えます。

Video = Struct.new(:price)

class Rental
  ADULT_DISCOUNT_AMOUNT = 100
  CHILD_DISCOUNT_RATE = 0.5

  attr_writer :person_kind

  def initialize(video, person_kind)
    @video = video
    @person_kind = person_kind
  end

  def price
    case @person_kind
    when :adult
      @video.price - ADULT_DISCOUNT_AMOUNT
    when :child
      @video.price * CHILD_DISCOUNT_RATE
    end
  end
end

利用者種別が2種類のみと少なく、また計算方法が単純な間は、priceメソッドは小さく収まります。 priceは呼び出しがあるたびに計算し直します。これは、person_kindが変更されることを許容するためです。

このクラスを使って、基本レンタル料が300円のビデオのレンタル料を計算する例を示します。

video = Video.new(300)
child_rental = Rental.new(video, :child)
adult_rental = Rental.new(video, :adult)
p child_rental.price # => 150.0
p adult_rental.price # => 200
adult_rental.person_kind = :child
p adult_rental.price # => 150.0

Strategyパターンを使う

さきほどの例では、料金計算方法の切り替えにcase文を使いました。十分にシンプルなコードになったので、コードの複雑さに関して問題はありません。

ここで、料金体系が追加される可能性について考えます。利用者種別が、20代、シニア、ポイントカード所持者など追加するかもしれません。case文でこれらの計算方法を切り替える場合、case文が巨大化していくことは明らかです。また、料金の計算方法ももっと複雑化するかもしれません。また、case文が追加されるたびに、Rental.priceのメソッド切り替えが壊れていないかに気をつける必要もあります。

そこで、利用者種別が追加されたとしてもStrategyパターンをつかって、Rentalクラスのに変更を加えなくて済む方法を考えます。

Video = Struct.new(:price)

class Pricer
  def initialize(video)
    @video = video
  end
end

class ChildPricer < Pricer
  DISCOUNT_RATE = 0.5

  def price
    @video.price * DISCOUNT_RATE
  end
end

class AdultPricer < Pricer
  DISCOUNT_AMOUNT = 100

  def price
    @video.price - DISCOUNT_AMOUNT
  end
end

class Rental
  def initialize(video, person_kind)
    @video = video
    self.person_kind = person_kind
  end

  def person_kind=(person_kind)
    # person_kindが変更されたらpricerも付け替える
    @person_kind = person_kind
    @pricer = pricer
  end

  def pricer
    pricer_klass = Object.const_get "#{@person_kind.to_s.capitalize}Pricer"
    pricer_klass.new(@video)
  end

  def price
    @pricer.price
  end
end

Pricer、AdultPricer、ChildPricerが導入されました。Pricerは抽象クラスで、AdultPricer、ChildPricerはその派生クラスです。person_kindとPricerクラスの派生クラスは、Rental.pricerにあるような方法でクラスを取得できるような名前を意図的に命名しています。

この方法により、新たなクラスを導入するという構造上の複雑さが増加した代わりに、変更に対して閉じていて拡張に対して閉じている、という開放/閉鎖原則に合致するの特性を得ることができました。

ここで得られたメリットは、主に下記の2つです。

  • Rentalの中にあった定数が各Pricerの中に移動した。
    • 本当に使うところに情報が移動したことで、計算方法に関する知識がPricer内で「閉じる」ことになった。
    • 各Pricerには、他の計算方法の知識が入ることはないので、定数名に利用方法を接頭辞として入れる必要がなくなり、名前を短くできた。
  • Rentalは計算方法についての具体的な知識が必要なくなった。
    • priceメソッドを持つインスタンスが@pricerに設定されていれば料金が計算できる。
    • その代わり、Pricerの生成方法、つまり自分にとって適切なPricerが何なのかを知る必要が出てきた。(FactoryメソッドをPricerに生やせば、この知識も外に出せる)

利用側のコードは、先程と全く同じです。

video = Video.new(300)
child_rental = Rental.new(video, :child)
adult_rental = Rental.new(video, :adult)
p child_rental.price # => 150.0
p adult_rental.price # => 200
adult_rental.person_kind = :child
p adult_rental.price # => 150.0

料金体系の追加や計算方法の複雑さに怯える必要はなくなりました。料金体系が追加された場合は、Pricerの派生クラスを作るだけで済み、Rentalクラスには手を加える必要がありません。また、計算方法が複雑化した場合も、変更するのはPricerの派生クラスの中だけになるため、意図しない部分の動作を変更してしまう心配もありません。

ここで気をつけるべきは、現時点でこの複雑さを導入する必要はあるのか?という点です。 結論から言えば、ほとんどの場合メリットはありません。もとのRental#priceメソッドを見ればわかるように、現時点での要件に対しては、case文を愚直に使うだけで十分わかりやすいコードです。これは例なのでお許しを。

Object#extendを使う

リファクタリング: Rubyエディションに示されている、「8.13 タイプコードからモジュールのextendへ」をStrategyパターンらしく利用する方法です。

Video = Struct.new(:price)

module ChildPricer
  DISCOUNT_RATE = 0.5

  def price
    @video.price * DISCOUNT_RATE
  end
end

module AdultPricer
  DISCOUNT_AMOUNT = 100

  def price
    @video.price - DISCOUNT_AMOUNT
  end
end

class Rental
  def initialize(video, person_kind)
    @video = video
    self.person_kind = person_kind
  end

  def person_kind=(person_kind)
    @person_kind = person_kind
    # pricerモジュールに生えているメソッドをインスタンスに生やす
    extend pricer
  end

  def pricer
    Object.const_get "#{@person_kind.to_s.capitalize}Pricer"
  end
end

Object#extendは、インスタンスでから呼び出すと、モジュールのメソッドをそのインスタンスだけのインスタンスメソッドとして生やします。

この方法では、Pricerのためにインスタンスを生成することなくRentalごとに違った料金計算方法を定義できます。extendを使ってメソッドの実装を動的に切り替えるという、非常にRubyっぽい方法に感じます。ここが一番伝えたいところです。Rentalにはpriceメソッドが映えることになりますが、インスタンスごとに違う実装になっているんです!そんなことができる言語はなかなかないので、委譲を利用するStrategyパターンが生まれたわけですね。めっちゃRubyっぽい!

(すごい感動したんですけど、文章で伝えるって難しいですね)

Strategyパターンとの違い、共通点、注意点を下記に示します。

Strategyパターンとの共通点

  • Pricerを追加するだけで料金種別の追加に対応できる
  • 各Pricerの料金計算を別々の塊に分けられるため、他の計算方法の影響を受けない

Strategyパターンとの違い

  • 料金計算をPricerに委譲するのではなく、Rental自身が行う
  • PricerはRentalのもつ構造について知っている必要がある(@videoが存在することなど)
  • Pricerのクラスの生成についての複雑さが取り除かれる

注意点

一度生成したインスタンスのPricerを変更する場合、上書きされなかったメソッドはRentalのメソッドとして残り続ける点に注意してください。

例として、ChildPricerをこのように定義します。

module ChildPricer
  DISCOUNT_RATE = 0.5

  def only_child_method
    'Only child'
  end

  def price
    @video.price * DISCOUNT_RATE
  end
end

料金の変更は反映されるものの、only_child_methodの呼び出しは可能なままです。これは、extendをつかってメソッドを追加しているだけなので、前に追加されたものを削除しているわけではないからです。

video = Video.new(300)
child_rental = Rental.new(video, :child)
puts child_rental.price # => 150
puts child_rental.only_child_method # => Only child
child_rental.person_kind = :adult
puts child_rental.price # => 200
puts child_rental.only_child_method # Only child

ここ数日のニュース・投稿に一言

↓のような経緯があり、今週のニュースをたくさん読んだ。

かなり数は絞ったが気になるニュースが多くあり、たくさん目を通してしまった。しかし読み返してみたら全然内容を覚えていないことに気付いた。せっかくなので、読んだものに一言ずつコメントをつけてみようと思う。

今後の情報収集の反省にもつながると思うので、構築した情報収集の仕組みについても書いておく。

情報の集め方

feedlyを使って気になるサイトを登録して毎日チェックした。特にインパクトの大きかったものはツイートした。

下記のサイトにあるようにPocketを組み合わせたりということも考えたのだが、feedlyの後で読む機能で十分ではないかと思ったため特に組み合わせてはいない。 Feedly / Pocket /Evernoteを使い、情報ファネルを構築して最小限の時間で最高の情報収集をする方法 | スリーク・トライブ

フィードの選び方

  • 習慣の記事数が100未満
  • カバー範囲が広そうなもの

気になった記事

ここに書くために読み直すということはせず、覚えているままに書いている。

Deno ってなんだっけ? https://qiita.com/kt3k/items/e1647683ad08ff6b6e95?utm_campaign=popular_items&utm_medium=feed&utm_source=popular_items

Node.jsの次みたいなやつ。Node.jsの作者が、Node.jsの反省を生かして作っているサーバーサイドJSの処理系。Promissが使えたり、node_modulesデカすぎ問題が解決されている。

感想

全然覚えていない。そもそも読んでいる記事の数が多すぎる気がする。反省。今後をお楽しみに。

非同期処理のことを考えていたら頭がバグった

この記事

Rubyで非同期処理しよ~、って思ってちょっと調べ始めた。調べれば調べるほど訳わかんなくなって来てしまい、頭が飽和してきた。もうなんかほんとによくわかんない。今の頭の中のダンプをとっておき、ちょっと頭をリフレッシュしたくなってきた。 という感じでただ吐き出すだけなので、この記事の信憑性は低いです。

Rubyの非同期処理API

Process

プロセスを作れる。つまりfork(2)できる。他の2つと比べるとメモリ使用量、生成コストともに大きい。

コピーオンライトがあるのでメモリをそのままコピーするわけではない。賢い。

Rubyで並列処理(ここでは時間的に厳密に同時に処理を行うことを指す)をするための唯一の方法。

Thread

スレッドを作れる。しかし、タイムアウト、I/O待ちを除いてマルチコアで並列処理ができない。な、なんだってー!というやりとりは、Rubyだとよくある話。Global VM Lock(GVL)という制約があるので、同じコアでしか処理できない。

別スレッドとして動いてはくれるので、同じコアだけど同時に動いてるように見える。並行処理だけど並列処理じゃない。

Fiber

軽量スレッドを作れる。作った軽量スレッドの実行、停止、再開をプログラマが指示する。動かしている間は呼び出し元をブロッキングする。

つまり別スレッドなんだけど、並列処理してくれるわけではないし、自動的に並行処理してくれるわけでもない。

使い分け

Rubyでの計算を高速化したい

Process。

IO待ちの時間を削減したい

Thread, Fiber, あるいはまた別の方法。

Threadは並列処理してくれない。でもIO待ちのときは並列に動ける。てことはIO待ちが発生するようなタスクだったらThreadが有効活用できそうだっていう話。Processでももちろんできるけど、Threadのほうがコストが小さい。Pumaはこれ。

でもよく考えると、Unixにはselectってやつがある。こいつを使うと、IO待ちになっている複数の場所を監視して、使えるようになったやつを選択できる。ということは、IO待ちの時間を有効に使おうと思ったとき、そもそもThreadを使わなくてもいいんじゃないかって話になってくる。

Fiberは、処理を実行途中で停止できるので、あ!IO待ちになりそうだ、Fiber.yieldでぬけよう。って感じのことができる。selectでIO待ちを回避できていることがわかれば、resumeする。これでブロッキングせずに処理を続けられる。selectをしたときに使える口が見つからなかったときは、新しいタスクを開始してあげれば、計算リソースを無駄にせずに済む。

コールバックを使う、というやり方をしてもよい。計算リソースの使われ方はだいたい同じ。IO待ちじゃなくなったときに実行する処理を登録しておく。ただ、コールバックはよくコールバック地獄っていう言葉で揶揄されるようにどうも人類には早すぎるらしい。

あれ、コールバック地獄への有効打って他にもないっけ

なんかこのへんでほんとに訳わかんなくなってきてしまった。よく理解していない知識を頭の中にとどめておくのって良くないですね。

EventMachine

というgemがある。このひと全く知らないのだけど、jsのコールバック付きの関数群みたいな関数群みたいなやつを提供してくれるやつっぽい。

Observerパターン

このタスクが終わったらこれやっといてね、っていうのを登録してくやつ。 これの実行タスクとして非同期処理を登録してやると、あとの後続処理をオブザーバーにかけるので、コールバック使わなくて済むのではなかったか。

EventMachineとObserverパターン組み合わせれば最強じゃね?みたいなことを思った。それがすべての元凶。

Future

ScalaのFuture。JSでいうPromise。非同期で処理して、後続処理を登録できる。 あれ、Observerパターンと似てる。Scalaでは登録された動作に対してスレッドを作る。作った瞬間勝手にどこかで処理されている、みたいな感じ。

2つのWebAPIからのデータを取得してまとめる、みたいなことをしようとしたとするじゃないですか。そうすると、外部との通信のコストを考えると非同期でやりたいなーと思うわけですね。

thread_a = Thread.new { api_a_call }
thread_b = Thread.new { api_b_call }
pp "I got #{thread_a.value} #{thread_b.value}"

こんな。なんかこれがダサいと思ったんですね。 APIは実際はこんな関数を入れます。時間が止まればいいです。

def api_a_call
  sleep 10
  'answer_from_api_a'
end

def api_b_call
  sleep 10
  'answer_from_api_b'
end

2つのAPIを並列に取得してあげると、10秒で帰ってきてくれるはずです。しかし、これってthread_a.valueがthread_bの定義よりも先にきてはいけないんですね。いけないというか、意図しない待ち方をして、20秒かかってしまうんです。

ここで、Futureを使ったらこんな書き方ができるんですね。

Future.new { api_a_call }
      .zip { Future.new { api_b_call } }
      .onComplete { |a, b| pp "I got #{a} and #{b}" }

わあかっこいい!素敵! いやでもこれFutureってどうやって実装するんだろう難しいなー。みたいなことを考えていました。

ReactiveExtentions

これってScalaのFutureの強い版ではなかったっけ。なんかAPI仕様がてんこ盛りで過ごそう。Futureとどう違うの君は。

だめだ、あたまがバグってきた

Fiber使わなくても、JSみたいなコールバッグを扱うAPIがあって、それを地獄に堕ちずに使える方法があるならそっちのほうがいいのでは。というようなことを思ったのです。

そうしたら、心当たりがあったのだけど、中途半端な知識しかなくて調べれば調べるほどわけわからなくなってきた。 ちょっと整理したいけど、溜め込みすぎたので一旦脳を休めたい。

SOLID, KISS, YAGNIをどう活かすのか

SOLID, KISS, YAGNIはいずれもソフトウェア開発において重要とされる原則です。原則というくらいなので、様々な形態を持つソフトウェア開発において共通して適用することができます。逆に言えば、特定のコンテキストに依存しない程度に抽象化されていると言えるのではないでしょうか。

私はこれらの原則を聞いたことはあるのですが、「わかるわ~」程度の刺さり方しかしませんでした。重要であるけれどいまいち具体的な対処がわからないこれら原則について、今一度おさらいします。

SOLID

オブジェクト指向を用いた開発で、メンテナンスが容易なプログラムを作るための5つの原則の総称。

最高にわかりやすい記事を見つけたので、私が説明をすることはDRYの原則に反します。 postd.cc

単一責任の原則の説明に、まさにActiveRecordのインターフェースを持ったクラスが良くない例として載っています。そうでした。Railsを使っているのですっかり忘れていましたが、本来ActiveRecordはたくさんの責務を持っているのでした。

自分なりの理解

  • 単一責任の原則: 一つのクラスが一つのことに関する知識を持つように設計する。
  • 開放閉鎖の原則: クラスを追加、拡張しようとしたとき、他のクラスもつられて修正しなくてもいいようにする。
  • リスコフの置換原則: サブクラスに依存した実装が存在しないようにする
  • インタフェース分離の原則: すべての実装クラスで必要なものだけをインターフェースにまとめる。必要なら細分化する。
  • 依存性逆転の原則: 実装でなくインターフェースに依存させる。実装クラスは引数として外から渡す。

SOLIDすべてを満たして設計~実装するのは、慣れていないとなかなかに骨の折れることのように思います。作るときの労力で言えば、新しくクラスを作るよりも、if文を追加してしまったほうがはるかに少ない、というケースが現実にはよくあるからです。

それでも、「ここはこの原則に反しているけど、適用するほど変更が加わりそうな場所でもない」とか、「この原則にしたがって設計したからこのインターフェースになっているんだな」といったことを考えることは、よりよい設計を目指す上で役に立ちそうです。

GoFデザインパターンを思い出してみると、これらの原則に忠実に設計する方法について述べられていたんだなあと気づきました。これらの原則を意識しつつGoFデザインパターンをおさらいすると、定着させることができるかもしれません。

KISS

"Keep it simple, stupid" の略。「シンプルにしておけバカ」という意味。

これを最初に聞いたとき、「お、おう。そうだよね。」くらいにしか思いませんでした。

ソフトウェア開発への適用についての詳しい記事を見つけたので、時間があるときに少しずつ読んでいこう。 thevaluable.dev

ソフトウェアは、すぐに複雑になりがちです。複雑なソフトウェアというのは何かというと、たくさんの要素によって構成されていたり、たくさんの要素とつながっているソフトウェアです。シンプルというのはその反対です。

ソフトウェアは、変更によってどんどん複雑になっていきます。このため、KISSの原則を一貫して徹底する、ということがソフトウェアのメンテナンスをかんたんにするという面で重要になってきます。

ソースコードをシンプルに保つ、というのは種々の技術によって実現できるでしょう。でも、システムをシンプルに保つのにはまた別の力が必要です。

とりあえず、KISSは本当に奥が深い、ということがわかったところでよしとしておきます。

YAGNI

"You ain't gonna need it"の略。「そりゃ必要にならんよ」てな意味。XPに関する格言で、必要になるまで実装するなという意味。 http://ja.wikipedia.org/wiki/YAGNI

これはめっちゃわかるし、必要のない機能を付け加えたくなる気持ちもめっちゃわかる。そういうコードの末路も見てきた。いらないコードは書かない、単純なことだけど気をつけていないと忘れがち。

References

下記の素晴らしい記事を参考にしました。

developer-roadmapを見ながら足りない知識を洗い出す

エンジニアになって2年半、ここのところ勉強したいことが多すぎてどれからやろうか、と考えてしまう時間が増えてきました。WebDveloper Roadmapに載っているものから優先して取り組んで行こうと思ったので記録しておきます。私のスキルの振り返りなので、他の方の参考になることは少ないと思います。

github.com Web Developer Roadmapは、Web開発者になるために必要な知識とアクションを順序付き表現した図です。 tajawal UAEでリードエンジニアを務める@kamranahmedse(https://twitter.com/kamranahmedse)さんによって作られました。

内容についてはこちらが参考になります。 Web Developer Roadmap 2018が2019年版になっていたので比較してみる - Qiita

自分の開発経験をおさらい

大学~大学院: C, JavaアルゴリズムUNIXシステム操作を学んだ。RaspberryPi+Railsで動画転送システムを作った。 社会人一年目: とある大規模システムのテスト設計やリリース戦略の設計をした 社会人二年目: JavaでWebシステムの裏で動くバッチを書いていた。 社会人三年目: RailsでWeb開発

あまりコードをゴリゴリ書くような開発の経験は少ないことに気づきました。Web開発っぽいことをし始めたのも最近です。ただ、使っている知識や技術はあまり変わっていません。

スキルの抜け

Introduction, Frontend Roadmap, Backend Roadmap, Devops Roadmapの4つのロードマップがあります。今回は、IntroductionとBackend Roadmapに記載のあるスキルのうち、自分が持っていないものをそれぞれ見ていきます。内容について概要を説明できて調べながら実践できそうなものを抜けていないことにして、実装経験がなくても良しとします。

Introduction

  • SOLID, KISS, YAGNI 聞いたことあるけど、内容が全く思い出せない。メンテしやすいコードを書くための心構えだったような。自然にやってる気もする。
  • Semantic Versioning 初耳。 記載のあるスキル11中2個抜けていた。

Backend Roadmap

こちらはアクションが主ですね。

  • Standards and Best Practices 今メインで使っているRubyは抜けてる。
  • Make and Distribute Some Package/Library 作ろ!ってなる状況にならなかったな~
  • Learn a NoSQL Database mongoDB使ったことはあるけど、いつ使い分けるの?とかわからん。
  • Caching キャッシュ実装することになったら、どうすればいいかわからん。
  • Creating RESTful APIs RESTはできるけどRESTfulはむずそう。
  • Authentication/Authorization Methodologies Oauthとベーシック認証くらいしかわからない
    • Token Authentication
    • JWT
    • OpenID
  • Message Brokers 何ができるかわからない
    • RabbitMQ
    • Kafka
  • Learn a Search Engine つかってみたい
  • Learn how to use Web Sockets だいたいの仕組みはしってるけど、これを使う実装できないです。
  • Look into Graph Databases なんですかそれは。
  • All things that weren't mentioned above ここが埋まることは一生ないだろう。DDD、SOAPは知ってるので、基礎を固める前にジャンプしちゃってた感。

24個のステップのうち、11個スキップしていた。

抜けまとめ

Introduction 2/11 Backend Roadmap 11/24

これからどうする?

今やりたいことが、非同期処理について学んだりとか、Railsへのコントリビュートとか、とかだったんだけど、その前に勉強しておいたほうがいいことが多そうだ。 Webエンジニアとして働いて行くために、何を学んだらいいかっていう観点が一貫していて、すごくいいロードマップでした。どうしてもプログラミング言語とか、フレームワークに対して興味が行きがちになってしまうけど、Webシステム全体を見た上で知識をつけていくほうが、Webエンジニアとしてはよさそう。

"Ask Me Anything" by DHH での質問と答え part2

ここにいる全員にあなたのようにStimulusとturbolinksを使うことを薦めますか?

まず第一に、ブログ、カンファレンスあるいはtwitterで人気のあるものと、実際に作られているアプリケーションで人気のあるものには違いがあります。 私は、それらの間には大きな違いがあるということに気が付きました。 今のところのブログ、カンファレンス、Twitterで人気のあるものは、react.jsやvue.jsあるいは他のツールを使ってたくさんのJavaScriptを記述するものです。 これらは、私には人気があるわけではなくとても誇張されているようにみえるのです。

私はそういったことの見極めについておそらくエキスパートです。 なぜなら、かつてRubyRailsの利用についてまた誇張されていたからです。 2000年台中期に、RubyRailsが人気になったとき、みんながそれについて語っていました。 しかし、実際に書いていたのは一部の人々だけです。

たくさんの人と話をしてみて、turbolinksやstimulusや少ないJavaScriptのアプローチが好きな人がたくさんいるということに気がついたんです。 そういった人々はあまり声を大にして主張しません。 皆さんは彼らの主張を、重厚なJavaScriptフレームワークを使っている人たち程は聞かないでしょう。 ときどき人々は、声を大にして主張されているものが人気のあるものだと誤解してしまいます。 もう一つ言いたいことは、ほとんどのアプリケーションはturbolinksやstimulusのみを使ってシンプルにスタートし、必要性が高まったとき、より洗練された、あるいは複雑なフレームワークに移行したほうがいいということです。

たくさんのアプリケーションがごく僅かなフォームを扱うだけであっても、とても洗練されていて複雑なJavaScriptを備えてスタートしてしまっていることは残念に思います。 ほとんどのアプリケーションの基本的な要求は、10年前と変わっていません。すなわちデータベースを扱うCRUDアプリケーションを作ることです。 そう言うと、それは良くない、もっと洗練されたアプリケーションを作ろうという野望を持つべきだと考える人がいます。 私はそれはくだらないことだと思います。

我々が今日作っているような情報技術における大多数は、良い方法に向かっているとは言えません。 なぜなら、我々は良くしているのではなく、たくさんのアプリケーションにJavaScriptフレームワークを導入するなどの理由で複雑にしてしまっているからです。 多くのアプリケーションは、よりシンプルな方法で開発することで少数の開発者でのデリバリーを高速化した場合などは、良くなったと言っていいでしょう。

BaseCampでの経験で言えば、我々が作ったすべての要素の中で極稀にそういった種類のフレームワークを必要とします。 BaseCampはどちらかといえば、react.js, vue.jsのようなフロントエンドフレームワークを使いサーバがJSONだけを返すようなスタイルが完璧にはフィットしない大規模アプリケーションの典型です。 私は、そのようなスタイルがフィットするアプリケーションは人々が思っているよりも一般的でないと考えています。

あなたはajaxやcoffee scriptを広めました。それって過ちだったのでは?

そうですね。私の失敗です。

それは当初からの計画でしたか?(この辺質問よくわからない)

もっと簡単にしようという思いがありました。 そして、多くのWebアプリケーション、ほとんどのWebアプリケーションは、JavaScriptを使うことで改善できるということに気づきました。もしJavaScriptを全く使えないなら、アプリケーションは良くないものになってしまいます。ただ、どれくらい多く使うかについては選択の余地があります。私はJavaScriptは少量のふりかけとして使うなら、うまく作用すると思います。少しの塩が(料理の)うえに乗っているだけなら風味を与えてくれますが、もしお皿が塩でいっぱいになってしまったら美味しくありません。これは現実にいつも起こっていることです。

人々は、新しいアイディアや進歩を手にしたとき、そのアイディアでどこまでもやって行きたいと思ってしまいます。だってこんなに素晴らしいアイディアなんだから、もしこれをすべてのことに使ったらどうだろうかと考えてしまうのです。そして、すべてのことに使おうと試行錯誤したあとで、悪いアイディアだったということに気づくのです。

今朝、yamlと、yamlXMLと同じ轍を踏んで悪いプログラミング言語として使い始めてしまうかについての記事を読みました。 初めは設定ファイルとして使いますが、次第に条件分岐や変数をはじめプログラミング言語で使える要素を使おうとし、それにもかかわらず設定ファイルとして扱おうとしてしまうために巨大な化け物に変貌してしまうのです。 これは、一度良いと思ったものを使い始めてしまうときの状態とすこし似ています。

良いものは、限られた範囲での特定の運用ではうまくいきます。しかし、詰め込みすぎてしまうと途端に破綻します。

RubyRailsについても、同じようなことが言えます。 これは最高だ!と思うと、適していないことであっても全て同じものを使ってしまうのです。 もともと考えられていたよりも、Rubyrailsの適用範囲は広がっているように思います。 一部はアーキテクチャの優先事項の問題です。

必ずしもいつも正しい方法があるとは考えないほうが良いでしょう。 Webの美しさは、バックエンドでどんなプログラミング言語でも利用できる多様性にあるのです。 私がWebアプリケーションをRubyで書く一方で、他の人はPythonで、また他の人はGoでもJavaでも書くことができて、その上ユーザーはそんなことを気にしないのです。これはwebの固有かつとても特別な特徴です。ほとんどのソフトウェア開発プラットフォームは、特定の開発環境に大きく依存します。iosのアプリを作りたいならばswiftを使わなければならないし、 Androidのアプリを作ろうと思ったらJava を勉強しなければなりません。Web ならそんなことは気にしません。C でもBASICでも、HTMLを生成できる言語なら何でも使うことができます。だから、どれぐらいのJavaScript が使われていればいいのかっていうことだってユーザーは気にしません。シンプルに作ろうが複雑に作ろうが素早く作ろうがユーザは気にしません。Web の素晴らしいところはそういった多様性を許容できるところです。他の開発プラットフォームと違って、人が違えばアイディアも違います。そういったところが Web の本当に美しいところだったと考えています。