不格好エンジニア

wordpress.comから引っ越しました。

現役のエンジニアが親友の為にプログラミングスクールを選ぶとしたら

はじめに

これからプログラミングを学ぼうという方が、実際に企業で勤務しているエンジニアと会話する機会は限られていると思います。そこに少し課題意識があったため、私見について書いてみました。誰かのお役に立てれば幸いです。

自分が、親友におすすめするとしたら、それなりの根拠が必要なので、自分なりに設定した選定基準について記述してみました。

おことわり

世の中にはいろんなニーズがあり、個々人によって経済力やこれまでのご経験、投下できる時間 なども異なる為、いろんな形態のスクールがあって構わないと考えています。

どんなに客観的に話しているつもりでも、筆者の意見もこれまでの経験、立場に影響を受けているのは間違いないので、そのつもりで読んで頂けたらと思います。

何を基準に選ぶか

主に下記2点を見ると思います。

  • 収益モデル
    • 受講者の満足度向上、キャリアにおける成功と運営元の収益が比例しやすい構造になっているか
    • お金がどのタイミングで発生しているか
  • 運営者、メンター、講師
    • 顔と名前を出している"現役の"エンジニアからフィードバックを受けられるか

カリキュラムなども選定基準に含まれると思いますが、これからプログラミングを学ぼうという方がカリキュラムを見て、その良し悪しを判断するのは難しいと思いますので、もう少し客観的に判断しやすいと思われるものを書いてみました。

収益モデル

結論からいえば、月額モデル、出世払いモデルで運営しているところなどが良いのではないかと思いました

  • 月額課金モデル

    • いつ止めても良いので、受講者にとっては金銭的リスクが小さいと思います
    • 仮に親友がプログラマにどうしても向いていないと発覚した場合に、途中で解約しても傷は浅いかなと思います
    • 運営元としては解約せずに継続してもらえると収益が上がるので、コンテンツの質を上げるインセンティブが働きやすい(誇大広告だけしても仕方ない)
    • netflixなども このモデルですし、実際にコンテンツの質を向上する努力を日々やっていると思います
  • 出世払い

    • 就職/転職後、お給料から一定割合を支払うモデル
    • 年収額に応じた率(7〜15%など)を一定期間、毎月支払う
    • めっちゃ低年収だったらお金はとられないようです (詳細は適宜、ご確認をお願いいたします)
    • 受講者が良いキャリアを歩んで、高い年収になれば運営元もハッピー
    • 真の意味で実力をつけてもらう必要があるので、例えば経験の浅い学生バイトを大量に雇ってビジネスをスケールさせる、、というインセンティブは働きにくいように見えます
    • 海外では先行事例があるようです

運営者、メンター、講師

親友におすすめするなら

  • 経営陣、メンターにそれぞれ第一線の、"現役の"エンジニアがいるか、
  • 本当にその人たちからフィードバックを受けられるか
  • 学生バイトの比率はどうか

等を見ると思います。

もはやプログラミングそのものは webや書籍を通じて一般論自体は学べてしまいます。

となると、差がつく部分は書籍からは学べない実務レベルでの経験、ということになると思います。

企業での開発経験がないと得られない知見としては、ほんの一例として、下記のようなものがあると思います。

  • 現場で忙しい先輩にレビューしてもらいやすいコミュニケーションになっているか
  • 性能向上が見込める書き方
  • セキュリティへの最低限の考慮
  • ユーザにとって使いやすい仕様になっているか
  • 簡単に機能追加できる設計になっているか
  • 読みやすいか

...などなど、列挙していけば幾らでもあります。基本的には実務経験のない方が上記の観点でフィードバックをするのは難しいように思えます。

良さげなところ

(観測範囲では)、条件に合致するスクールは以下の2つでした。 ちなみに筆者は下記の団体と何のつながりもありません。

筆者が実際に入学した事はありませんので、経験として語る事はできません。 ただ最初に大金を支払うモデルでは無さそうなので、リスクも少ないのではないかと思いました。


オブジェクト指向プログラミング特集を読みたくて 〜Software Design 2021年3月号〜

チーム内に新しいメンバーが増えてきて、クラス設計について議論する機会が増えてきました。冒頭特集「Javaでもう一度学び直すオブジェクト指向プログラミング」を目当てに『Software Design 2018年12月号』を購入しました。この雑誌は定期的に購入しています。


いま自分は、Goを使用したmicroservice構築に従事しています。GoだとRubyにおけるRailsのような絶対的な位置づけのフレームワークもなく、そのため、どのレイヤに修正を加えるか?インタフェース、クラスの設計をどうするか?という所がしばしば議論になります。

いくつかの議論を実際に経験してきた教訓として、デザインパターンを覚えるよりも、いくつかの大原則を理解し、なぜその変更が良いのか(あるいは悪いのか)を言語化できる事が大事なのかな、という気がしています。

本書では JavaのString, LocalDatを題材として、クラス設計で重要な3つの原則について学ぶことができます。
クラスはイミュータブルな設計にすべき、という原則が歴史的背景とともに紹介されており、理解が深まりました。



rbenvを流し読み

注)誤ったことを書いているかもしれません。

動機

rubyで仕事してると職場でも頻繁に使うものなので、中身を理解したくなった。

version

1.1.1

ドキュメントを読んでみる

github.com

冒頭の重要そうなところをピックアップしてみます

No headaches running apps on different versions of Ruby. Just Works™ from the command line and with app servers like Pow. Override the Ruby version anytime: just set an environment variable.

異なるruby versionで動くアプリに頭痛で悩まされることがない。Powのようにコマンドラインで動かすだけ。いつでもRuby versionを上書きできる。環境変数でsetするだけだ。

Rock-solid in production. Your application's executables are its interface with ops. With rbenv and Bundler binstubs you'll never again need to cd in a cron job or Chef recipe to ensure you've selected the right runtime. The Ruby version dependency lives in one place—your app—so upgrades and rollbacks are atomic, even when you switch versions.

productionで頑丈だ。rbenvとBundler binstubsにより、cron jobの中でcdしたり、chep recipeで正しいruntimeを選択していることを保証する必要はない。Ruby versionの依存はただ一箇所、あなたのアプリに存在しているので、upgradesやrollbackはatomicはversionを切り替えるときでもatomicに行える。

One thing well. rbenv is concerned solely with switching Ruby versions. It's simple and predictable. A rich plugin ecosystem lets you tailor it to suit your needs. Compile your own Ruby versions, or use the ruby-build plugin to automate the process. Specify per-application environment variables with rbenv-vars. See more plugins on the wiki.

ecosystemがしっかりしている、ということが書いてある。

ざっくり眺める

.
├── bin
├── completions
├── libexec
├── rbenv.d
│    └── exec
│         └── gem-rehash
├── src
└── test
    └── libexec
  • bin/ メインのロジックはここに入ってるようです。
  • libexec rbenv localなどに対応するファイル群。

bin/rbenv

  command_path="$(command -v "rbenv-$command" || true)"
  if [ -z "$command_path" ]; then
    if [ "$command" == "shell" ]; then
      abort "shell integration not enabled. Run \`rbenv init' for instructions."
    else
      abort "no such command \`$command'"
    fi
  fi

rbenv-local, rbenv-init などの呼び出しはここで行っているようです。メインのロジックがここに入っているようですね。

rbenv initを実行したときに内部で起きていることを理解すれば、大体あとはわかる気がしたので、そのあたりを中心に追ってみると良さそうです。

いきなり動作を把握するのは難しそうなので、rbenv-initのテストを読みにいきます。

@test "adds shims to PATH" {
  export PATH="${BATS_TEST_DIRNAME}/../libexec:/usr/bin:/bin:/usr/local/bin"
  run rbenv-init - bash
  assert_success
  assert_line 0 'export PATH="'${RBENV_ROOT}'/shims:${PATH}"'
}

rbenv initでshimsをPATHに追加します。

@test "outputs sh-compatible syntax" {
  run rbenv-init - bash
  assert_success
  assert_line '  case "$command" in'

  run rbenv-init - zsh
  assert_success
  assert_line '  case "$command" in'
}

rbenv initでshellに互換性のある文字列を出力します。

@test "creates shims and versions directories" {
  assert [ ! -d "${RBENV_ROOT}/shims" ]
  assert [ ! -d "${RBENV_ROOT}/versions" ]
  run rbenv-init -
  assert_success
  assert [ -d "${RBENV_ROOT}/shims" ]
  assert [ -d "${RBENV_ROOT}/versions" ]
}

rbenv initでshimsとversionsディレクトリを作成します。

rbenv-init

{ echo "# Load rbenv automatically by appending"
    echo "# the following to ${profile}:"
    echo
    case "$shell" in
    fish )
      echo 'status --is-interactive; and source (rbenv init -|psub)'
      ;;
    * )
      echo 'eval "$(rbenv init -)"'
      ;;
    esac
    echo
  } >&2

表示される指示をみると、.bashrc, .bash_profile なりに eval "$(rbenv init -)" を書いておけ、とあります。 つまり、rbenv init - の結果が文字列(シェルスクリプト)として返されるので、これをevalすることによって、PATHを通したりしてるみたいですね。

参考

RubyGemコードリーディングのすすめ

rbenv + ruby-build はどうやって動いているのか - takatoshiono's blog

rbenvコードリーディング

rbenvの仕組み | Web Memorandum

次に読みたい

Ruby

wicked_pdf, request_store, bullet, wkhtmltopdf, sidekiq, active_support, webrick, draper, jbuilder, rails/active_job, rails/spring, capistrano, omniauth, committee etc..

iOS

RxSwift, AFNetworkinig, SDWebImage, Alamofire, MBProgressHUD, Masony, SwiftyJSON, MJRefresh, CocoaLumberjack, Realm, SsnapKit, Kingfisher


Rails::Railtieを流し読み

Rails::Railtieを流し読み

動機

request_storeを読んでいる途中で、結局途中でrailtiesにブチあたり、この辺ちゃんと読んだことないのが気持ち悪いな、という気持ちでコードリーディングを始めてみました。

version

2.3.0

ドキュメントを読んでみる

Ruby on Rails API

個人的に重要だと思った点をピックアップしてみます。

core of the Rails framework and provides several hooks to extend Rails and/or modify the initialization process.

rails frameworkのコアになっており、初期化プロセスを拡張するための仕組みを提供している。

Every major component of Rails (Action Mailer, Action Controller, Active Record, etc.) implements a railtie. Each of them is responsible for their own initialization.

主要なcomponentはrailtieを実装している。なるほど、Gemを読んでいく上ではこの辺の仕組みをわかってないとしんどそうです。

To add an initialization step to the Rails boot process from your railtie, just define the initialization code with the initializer macro: If specified, the block can also receive the application object, in case you need to access some application-specific configuration, like middleware:

自作のrailtieをRails の初期化プロセスに組み込むには、initialization codeを書くだけです、と。blockはapplication objectを受け取ることができて、application固有の設定にアクセスできます。

request_storeのソースコードをみてみると、こうなっております。

module RequestStore
  class Railtie < ::Rails::Railtie
    initializer "request_store.insert_middleware" do |app|
      if ActionDispatch.const_defined? :RequestId
        app.config.middleware.insert_after ActionDispatch::RequestId, RequestStore::Middleware
      else
        app.config.middleware.insert_after Rack::MethodOverride, RequestStore::Middleware
      end

      if ActiveSupport.const_defined?(:Reloader) && ActiveSupport::Reloader.respond_to?(:to_complete)
        ActiveSupport::Reloader.to_complete do
          RequestStore.clear!
        end
      elsif ActionDispatch.const_defined?(:Reloader) && ActionDispatch::Reloader.respond_to?(:to_cleanup)
        ActionDispatch::Reloader.to_cleanup do
          RequestStore.clear!
        end
      end
    end
  end
end

bulletはこんな感じです。

  if defined? Rails::Railtie
    class BulletRailtie < Rails::Railtie
      initializer 'bullet.configure_rails_initialization' do |app|
        app.middleware.use Bullet::Rack
      end
    end
  end

ざっくり眺める

Rails::Railtieはabstract class

直接インスタンス化はできないことがわかります。

rails/railtie.rb at master · rails/rails · GitHub

module Rails
  class Railtie
    # ...
    def initialize #:nodoc:
      if self.class.abstract_railtie?
        raise "#{self.class.name} is abstract, you cannot instantiate it directly."
      end
    end
  end
end  

主要なメソッドは、rake_tasks, console, runner, generatorsかな。。 ここで格納されたblockが、後に #each_registered_block(type, &block) の中で使用されるようです。

module Rails
  class Railtie
    # ...

      def rake_tasks(&blk)
        register_block_for(:rake_tasks, &blk)
      end

      def console(&blk)
        register_block_for(:load_console, &blk)
      end

      def runner(&blk)
        register_block_for(:runner, &blk)
      end

      def generators(&blk)
        register_block_for(:generators, &blk)
      end
      
      private
        # receives an instance variable identifier, set the variable value if is
        # blank and append given block to value, which will be used later in
        # `#each_registered_block(type, &block)`
        def register_block_for(type, &blk)
          var_name = "@#{type}"
          blocks = instance_variable_defined?(var_name) ? instance_variable_get(var_name) : instance_variable_set(var_name, [])
          blocks << blk if blk
          blocks
        end
        

処理の流れを追ってみる

エントリポイント

lib/rails/initializable.rb

initializerメソッドはRails::Initializableモジュールに定義されており、ここでInitializerインスタンスが initializersに格納されています。

module Rails
  module Initializable
    module ClassMethods
...
      def initializers
        @initializers ||= Collection.new
      end
    
      ..
      def initializer(name, opts = {}, &blk)
        raise ArgumentError, "A block must be passed when defining an initializer" unless blk
        opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
        initializers << Initializer.new(name, nil, opts, &blk)
      end
    end
  end
end  

lib/rails/initializable.rb

Rails::Applications#initialize!が呼ばれると、 initializersはRails::Initializable#run_initializersの中でrunされていきます。 tsortが何なのかは、Ruby's TSort explained を読めばわかりそうなのですが、いったんここでは先に進みます。

config/environment.rb

# Load the Rails application.
require_relative 'application'

# Initialize the Rails application.
Rails.application.initialize!

lib/rails/application.rb

module Rails
  class Application < Engine
...
    # Initialize the application passing the given group. By default, the
    # group is :default
    def initialize!(group = :default) #:nodoc:
      raise "Application has been already initialized." if @initialized
      run_initializers(group, self)
      @initialized = true
      self
    end
  end
end  

lib/rails/initializable.rb

module Rails
  module Initializable
...
    def run_initializers(group = :default, *args)
      return if instance_variable_defined?(:@ran)
      initializers.tsort_each do |initializer|
        initializer.run(*args) if initializer.belongs_to?(group)
      end
      @ran = true
    end
...

initializersはRails::Application#initializersとRails::Initializable#initializers があるが、モジュールのメソッドよりクラスで定義されているメソッドが優先され、Rails::Application#initializers が実行されます(実際に動かして確認しました)。

lib/rails/application.rb

module Rails
  class Application < Engine
...
   def initializers #:nodoc:
      Bootstrap.initializers_for(self) +
      railties_initializers(super) +
      Finisher.initializers_for(self)
    end
  end  
end  

Bootstrap initializersは下記の通り、applicationの準備をするものが返されます。 [:load_environment_hook, :load_active_support, :set_eager_load, :initialize_logger, :initialize_cache, :initialize_dependency_mechanism, :bootstrap_hook, :set_secrets_root]

railties initializersは Rails::Application 自身に定義されたものが返されます。

(byebug) initializers.map(&:name) [:set_load_path, :set_autoload_paths, :add_routing_paths, :add_locales, :add_view_paths, :load_environment_config, :prepend_helpers_path, :load_config_initializers, :engines_blank_point, :append_assets_path]

#railties_initializersの中身を少し掘ってみていきます。#ordered_railtiesは何をしているのでしょうか?

    def railties_initializers(current) #:nodoc:
      initializers = []
      ordered_railties.reverse.flatten.each do |r|
        if r == self
          initializers += current
        else
          initializers += r.initializers
        end
      end
      initializers
    end
    
    # Returns the ordered railties for this application considering railties_order.
    def ordered_railties #:nodoc:
      @ordered_railties ||= begin
        order = config.railties_order.map do |railtie|
          if railtie == :main_app
            self
          elsif railtie.respond_to?(:instance)
            railtie.instance
          else
            railtie
          end
        end

        all = (railties - order)
        all.push(self)   unless (all + order).include?(self)
        order.push(:all) unless order.include?(:all)

        index = order.index(:all)
        order[index] = all
        order
      end
    end
    

lib/rails/initializable.rb

ここで、定義されたブロックが実行されてますね。

class Initializer
  def run(*args)
    @context.instance_exec(*args, &block)
  end
end

次に読みたい

wicked_pdf, request_store, bullet, wkhtmltopdf, sidekiq, active_support, rbenv, webrick, draper, jbuilder, rails/active_job, rails/spring, capistrano, omniauth, committee etc..

参考

Railtieのinitializerが読み込まれる仕組み

Introduction to Railties

The Rails Initialization Process — Ruby on Rails Guides

Configure your gem the Rails way with Railtie

ブログに技術書の内容を丸写しする問題点と、オリジナルなコンテンツを書くためのアイデア - give IT a try

RubyGemコードリーディングのすすめ

#299 Rails Initialization Walkthrough (pro) - RailsCasts

vendor/bundle/ruby/2.3.0/gems/rails-4.2.5/guides/source/initialization.md · master · ypleung / cmpt276ass2 · GitLab

Ruby's TSort explained

[Ruby入門] 11. クラスを拡張する① モジュールのお話

プラットフォームの教科書 を読んだ

プラットフォームの教科書 を読んだ。

この本を読んだ自分の背景というと、普段はスタートアップ企業でグロースハックやWebアプリケーション、モバイルアプリの開発などに従事している。

プラットフォームビジネスの勝敗を分ける要因は当然、技術的な要因だけではない。どうやってWinner Takes Allの状況を突き崩していくか。"次"の打ち手を考えるヒントを得られることを期待して購入した。

要約

Winner Takes Allに見えるプラットフォーム・ビジネスにおいても、ひとり勝ちの状況はある日、突然くつがえされることがある。なぜ、それが起きるのかを近年の事例(任天堂 vs スマホゲーム、スマホシフトをテコにしたLINEの成長)をもとに解説している。

面白かった章とその理由

02 レイヤー構造化

プラットフォームのコンセプトを戦略にうまく取り入れた事例として、仮面ライダーのベルトや妖怪ウォッチのメダルが紹介されている。仮面ライダーの変身用ベルトの機能の変遷から、ビジネスモデルの進化を考察していて、非常に興味深い。

13 5つの対抗策

Winner Takes Allの進行を妨害する5つの戦略について解説している。戦略としては以下の通り。

  • 収益モデルの破壊と拡張
  • プラットフォーム包囲
  • プラットフォーム間橋渡し
  • プラットフォーム互換
  • プラットフォーム連携

ほとんどの事業者は、Winner ではなく、次のWinnerになりたい側(追う側)であるはずなので、興味深く読める方が多いはず。

理解できなかったところ

なぜ産業がレイヤー構造化するのか、疑問が生じたので調査してみた。下記のスライドがわかりやすい。

バリューチェーン戦略論からレイヤー戦略論へ

インターネットの浸透により、売り手と書い手が同一空間/同一時間に取引を行う必要がなくなったから、という事らしい。

仕事に活かせそうな知識、活かせそうな状況と活かし方

自社の状況に当てはめると、如何にリソースを集中投下するか、選択と集中について、リソースの潤沢な大企業よりもシビアに判断していく必要がある。

その視点でみると、特に使えそうな考え方は以下の3点。

  • マチュアエコノミー(UberAirbnbの事例のように、アマチュアを事業者として参画させ、ゲームのルールを変える。)

  • 信頼性と信用の確保(機能の豊富さを売りにするのではなく、信頼と信用を担保する仕組みを徹底的に磨き込む。)

  • 収益モデルの破壊と拡張(既存プレイヤーの収益源を"無料化"によって破壊する。)

まだよく理解しきれていない所も多いので、時々手に取って読み返したい。

コーディング面接対策サイトCodilityの練習問題を解いてみた(MinMaxDivision)

問題

配列AをK個に分割し、それぞれ分割したブロックの和を求める。 すべての配列の要素は5以下。

A = [2, 1, 5, 1, 2, 2, 2], K = 3, M = 5 なら

[2, 1, 5, 1, 2, 2, 2], , なら、分割したブロックの和の最大値 15; [2], [1, 5, 1, 2], [2, 2] なら、分割したブロックの和の最大値 9; [2, 1, 5], [], [1, 2, 2, 2] なら、分割したブロックの和の最大値 8; [2, 1], [5, 1], [2, 2, 2] なら、分割したブロックの和の最大値 6.

それぞれのパターンで算出した分割したブロックの和の最大値の中で、最小値を求めることがgoal。

Codility

回答

K == 1 なら各要素の総和(high)に K == 配列の長さなら、各要素の中で最大のもの(low)が答えに それ以外の場合なら、lowとhighの間に答えがあるはずなので、BinarySearchで、ちょっとずつ計算する

#A = [2,1,5,1,2,2,2]

def solution(k, m, a)
  if k == 1
    return a.inject(0) {|sum, x| sum + x }
  elsif k == a.length
    return a.max
  else
    # use binary search
    low = a.max
    high = a.inject(0) {|sum, x| sum + x }

    while (low <= high) do
      mid = (low + high) / 2
      p "mid = #{mid}"
      if check(a, k, mid)
        high = mid - 1
      else
        low = mid + 1
      end
    end
    return low
  end
end

def check(a, k, mid)
  sum = 0
  blocks = 0

  a.each {|e|
    if ( (sum + e) > mid )
      sum = e
      blocks += 1
      return false if blocks >= k
    else
      sum += e
    end
    #puts "sum = #{sum}, blocks = #{blocks}, k = #{k}, mid = #{mid} "
  }
  return true
end

p solution(3,5,A)

コーディング面接対策サイトCodilityの練習問題を解いてみた(TapeEquilibrium)

問題

テーブを前半と後半に切断して差分を比較し、最も少ない差分を返す

Test results - Codility

回答

headにテープの前半部分を、tailにテープの後半部分を格納する。 インデックスを動かすごとにdiffを再計算する。

def solution(a)
  sum = a.reduce(:+)

  head = a[0]
  tail = sum - a[0]
  min_diff = 10000

  for i in 1...a.length
    diff = (head-tail).abs
    min_diff = [min_diff, diff].min
    head += a[i]
    tail -= a[i]
  end
  min_diff
end
# debug

arr = [3,1,2,4,3]
p solution(arr)

arr1 = [-1, 1]
p solution(arr1)

その他

rubyのreduce知らんかった。。。