よこまるろぐ

学び直し中元エンジニアのアウトプットブログ。技術やものづくり。たまに猫。

自分だけが落ちるテスト 〜DNS、お前だったのか〜

これは「フィヨルドブートキャンプ Advent Calendar 2025」の4日目の記事です。 https://fjord-calendar.jp/calendars/2025

昨日の記事はこちらになります。

urawawaru.hatenablog.com

きたろうさんは輪読会などで知識面でたくさんサポートしてくださりいつも感謝しています! 今回の記事でも学習の内容を実際にお仕事に活かしている姿が本当に素晴らしく、自分も見習いたいと思いました!


目次

はじめに

私はFBCに2024年1月に入会し、現在チーム開発のプラクティスを進めています。

チーム開発プラクティスでは、自分たちが学習に利用しているブートキャンプアプリを開発します。

bootcamp.fjord.jp

github.com

プラクティスを進める中で、自分の環境だけテストが通らないという問題に遭遇しました。

この記事では、その原因と背景にあったRuby標準ライブラリResolvのDNS解決について調べた内容をまとめます。


起きたこと

自分の環境で、以下Minitestのモデルテストが毎回失敗するという事象に遭遇しました。

このテストに関係する部分を特に触ったわけでもなかったためとても困惑していました。

分報で呟いてみたところ、他の生徒の環境では正常に通っているらしく、どうやら自分だけがこのテストに落ち続けているということが分かりました。

# テスト
module LinkChecker
  class CheckerTest < ActiveSupport::TestCase
    test '.valid_domain? returns false with invalid domain' do
      assert_not Checker.valid_domain?('invalid.invalid') # RFC 2606で予約済みの無効なドメイン名(こちらはテストが通る)
      assert_not Checker.valid_domain?('') # テストが通らない!!
    end
   end
end
# テスト結果
Failure: LinkChecker::CheckerTest#test_.valid_domain?_returns_false_with_invalid_domain
Expected true to be nil or false

なぜだ…。

テスト対象のモジュールについて

原因を探るために、まずはテスト対象のモジュールについて確認を行いました。 LinkChecker::Checkerは、ブートキャンプアプリ内に貼られているURLが有効かどうかを確認するためのモジュールです。 その中のChecker.valid_domain?(domain)は、引数として渡された文字列が有効なドメインかどうかを判定します。

空文字はドメインとしておかしいはずなのでfalseが返ってほしいのですが、私の環境ではなぜかtrueが返っていたため上記のテストが通らなくなっていました。

モジュールのコードを見てみると、中身を確認するとRubyの標準ライブラリであるResolvgetaddressというメソッドに引数としてドメイン名を渡して呼び出しています。

require 'net/http'
module LinkChecker
  module Checker
    # 省略
    def valid_domain?(domain)
      Resolv.getaddress(domain)
      true
    rescue Resolv::ResolvError
      false
    end
    # 省略
  end
end

CIでは問題なく通っていたため、これがいわゆるおま環か…と一瞬あきらめそうになりましたが、原因がわからないまま放置するのも気持ち悪かったのでResolvの挙動を調べてみることにしました。

自分の環境

- macOS: Sequoia 15.5(当時)
   ※ Tahoe 26.1 でも再現
- Ruby: ruby 3.1.6p260 (2024-05-29 revision a777087be6) [arm64-darwin24](当時) 
   ※ Ruby 3.4.3(2025-04-14 revision d0b7e5b6a0) +PRISM [arm64-darwin24] でも再現
- Rails: Rails 6.1.7.10(当時) 
   ※ Rails 7.2.2.2 でも再現
- ネットワーク: 回線事業者:NTT東日本 フレッツ光回線(IPv4/IPv6)

ここでの事象は環境依存なので、別のmacOS / 別のネットワークでは再現しない可能性があります。

調査

Rubyの標準ライブラリResolvを調べる

まずはResolvがどのような役割なのか"るりま(Rubyリファレンスマニュアル)"を見てみることにしました。

docs.ruby-lang.org

library resolv

resolvはDNSを使ってドメイン名からIPアドレスを調べるなど、名前解決を行うための Ruby標準ライブラリです。

Ruby製なので、Rubyのスレッドと呼ばれる同時進行の仕組みと相性が良い作りになっているようです。

class Resolv

Resolvクラスは、リゾルバ(=名前解決を行うクライアント側)をまとめて扱うためのクラスです。

このクラス自身が名前解決を行うわけではなく、Resolv.newに渡された複数のリゾルバへ順番に問い合わせる役割を持っています。

Resolv.getaddress(name)

Resolvのクラスメソッドで、ホスト名の文字列nameからIPアドレスをルックアップ(=ドメイン名から必要なIPアドレスを探しにいく処理)を行い、結果の最初のアドレスを返します。

ルックアップは/etc/hosts、DNSの順で行います。ルックアップに失敗したときにResolv::ResolvErrorが発生します。

/etc/hostsはホスト名とIPアドレスを対応させるためのファイルです。 ※ 2026-05-22追記: ここでの/etc/hosts→ DNSという順番は、RubyのResolvのデフォルト設定における話です。 OS全体の名前解決では/etc/nsswitch.conf(Linuxなどで名前解決の参照順を決める設定)やmDNS(ローカルネットワーク内で名前解決する仕組み) なども関係するため、Resolvと完全に同じ挙動になるとは限らないようです。

Resolv.getaddressを試す

実際にResolv.getaddressの挙動を確認するために、Resolv.getaddress("")Resolv.getaddress("invalid.invalid")rails cで試してみました。

なんと、空文字を渡したらIPv6アドレスが返ってきていることがわかりました。

Resolv.getaddress("")
# => "2404:1a8:ff43:1912::b"

一方で無効なドメインはちゃんと例外が返ってきています。

Resolv.getaddress("invalid.invalid")
# => Resolv::ResolvError

whoisコマンドを試す

このIPv6アドレスが何なのか調べてみるためにwhoisコマンドを実行しました。

whoisコマンドは、ドメイン名やIPアドレスなどの公開済みの登録情報を検索するためのコマンドです。

$ whois "2404:1a8:ff43:1912::b"

inet6num:       2404:1a8::/32
netname:        NTTEAST-NGN
descr:          NTT EAST, Inc.

whoisコマンドを実行した結果、このIPv6アドレスはNTT東日本(自分の契約している回線事業者)が管理しているアドレスだということが分かりました。

/etc/hostsを見てみる

いくら契約しているとはいえ、空文字がなぜNTT東日本のアドレスに繋がるかこの時点では見当がつきませんでした。

さらに調査をするためにResolv.getaddress(name)の内容を見直すと/etc/hosts→DNSの順でルックアップすると書いてあったため、まずは/etc/hostsを見てみます。

$ cat /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1   localhost
255.255.255.255 broadcasthost
::1             localhost

/etc/hostsの内容を1つ1つ確認してみます。

127.0.0.1 localhost

自分自身を指すIPv4アドレス(ローカルループバックアドレス)。localhostは必ずこのPC自身を示します。

255.255.255.255 broadcasthost

IPv4のブロードキャスト用アドレス。ネットワーク全体にパケットを送るための特殊アドレスです。

::1 localhost

自分自身を指すIPv6アドレス(ローカルループバックアドレス)。


上記の情報の中には空文字("")に紐づいているIPアドレスはないため、DNSに問い合わせるときに何かが起きていそうです。

DNS解決の流れを見る

DNSに問い合わせる際にResolvがどのファイルを参照しているのか確認する必要があるため、Resolv.getaddressの中身を確認します

Resolv.getaddress

Resolv.getaddress(name)のコードを確認すると、DefaultResolverに処理を渡していることがわかります。

# https://github.com/ruby/ruby/blob/fcf3939780972d587b18afc26c4abd2da2c0b7ec/lib/resolv.rb#L43
def self.getaddress(name)
  DefaultResolver.getaddress(name)
end

DefaultResolver

このDefaultResolverResolv.newのデフォルト設定がそのまま使われます。

# https://github.com/ruby/ruby/blob/fcf3939780972d587b18afc26c4abd2da2c0b7ec/lib/resolv.rb#L3484
DefaultResolver = self.new

Resolv.new のデフォルト設定

Resolv.newを引数なし(=nil)で呼び出した場合、内部では次の 2 つのリゾルバが自動的に設定されます。

  • Hosts.new

    • /etc/hostsを参照し、端末に設定されたドメイン名→ IPアドレスの対応から名前解決
  • DNS.new(nil)

    • /etc/resolv.confをもとにDNSへ問い合わせて名前解決

※ ここの引数のあたりのコードが複雑なのですが、基本的にはresolversにはデフォルト(nil)が渡されるようです。

  # https://github.com/ruby/ruby/blob/fcf3939780972d587b18afc26c4abd2da2c0b7ec/lib/resolv.rb#L89
def initialize(resolvers=(arg_not_set = true; nil), use_ipv6: (keyword_not_set = true; nil)) # resolversが渡されなかったらarg_not_set = true、use_ipv6が渡されなかったらkeyword_not_set = true
    if !keyword_not_set && !arg_not_set # 引数がどっちも渡ってきてたら警告を表示する
        warn "Support for separate use_ipv6 keyword is deprecated, as it is ignored if an argument is provided. Do not provide a positional argument if using the use_ipv6 keyword argument.", uplevel: 1
    end

    @resolvers = case resolvers 
    when Hash, nil
        [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(resolvers || {}))] 
    else
        resolvers
    end
end

ドキュメントにも引数がnilのときは/etc/resolv.confを読み込むと書いてあります。

https://github.com/ruby/ruby/blob/fcf3939780972d587b18afc26c4abd2da2c0b7ec/lib/resolv.rb#L312
    # Creates a new DNS resolver.
    #
    # +config_info+ can be:
    #
    # nil:: Uses /etc/resolv.conf.

/etc/resolv.confを確認する

自分の端末の/etc/resolv.confの中身を見ていきます。

$ cat /etc/resolv.conf

search flets-east.jp iptvf.jp
nameserver 240b:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx<契約している回線事業者のIPv6 DNS>
nameserver 192.xxx.xxx.xxx<契約している回線事業者のIPv4 DNS>

nameserverとは

nameserverは名前解決を行うDNSサーバーのIPアドレスを指定する項目です。 複数指定可能で、書いてある順番で問い合わせを行います。

nameserver 240b:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx<契約している回線事業者のIPv6 DNS>
nameserver 192.xxx.xxx.xxx<契約している回線事業者のIPv4 DNS>

searchとは

searchは名前解決においてホスト名の部分だけ入力した時に、自動でドメインの部分を補完をしてくれる項目です。

search flets-east.jp iptvf.jp

例えばfooというドメイン名を解決しようとした場合、searchで上記を設定すると以下の順でDNSに問い合わせを行ってくれます。

  1. foo.flets-east.jp
  2. foo.iptvf.jp

今回起きていたのは、このsearchにより空文字("")が以下のように補完されてしまい "flets-east.jp"のIPアドレスを取得しに行ってしまうことが原因のようでした。

""  + flets-east.jp = "flets-east.jp"

実際にResolv.getaddress("flets-east.jp")を確認してみると同じIPv6アドレスが返ってくることがわかりました!

valid_domain?("")trueになり、テストが落ちていたことも納得できます。

bootcamp(dev)> Resolv.getaddress("")
=> "2404:1a8:ff43:1912::b"
bootcamp(dev)> Resolv.getaddress("flets-east.jp")
=> "2404:1a8:ff43:1912::b"

まとめ

  1. Resolv.getaddress("")を呼ぶ
  2. /etc/hostsにないのでDNSを見に行く
  3. DNSの設定は/etc/resolv.confを参照する
  4. search flets-east.jpが入っていたため、空文字が補完されflets-east.jpで名前解決しに行く
  5. 契約している回線事業者(NTT東日本)のDNSがIPv6アドレスを返す
  6. Resolv.getaddress("")からIPv6アドレスが返ってきたので、LinkChecker::Checker#valid_domain?("")trueになる
  7. テストが落ちる

つまり、自宅のネットワークが返すDNSの挙動によってテストが落ちていた。

DNS、お前だったのか。

〜完〜

他のネットワークを試す

とはいえ、他のネットワークでどうなるのか見てみないと分からないと思い、試しにiPhoneのテザリング(au回線)に切り替えてみました。

$ cat /etc/resolv.conf
#
# macOS Notice
#
# 省略

nameserver <iPhoneテザリングのDNS(IPv4) >
nameserver <iPhoneテザリングのDNS(IPv6) >

/etc/resolv.confの内容が変わりsearchが消えていました。 この場合、空文字が渡ってきてもドメインが補完されなくなるはずです。

結果として元々想定していたエラーが返ってくるようになりました!もちろんテストも落ちません。

Resolv.getaddress("") 
# => Resolv::ResolvError

テストとしてどう扱うべきか?

接続するネットワークによって結果が変わる可能性があるテストを行う場合、どのような対応ができるかを考えてみました。

案1. ネットワークに依存するケース(今回でいう空文字の場合)はテストから切り離す

空文字とは別にinvalid.invalidという無効なアドレスでチェックできているため、特定のネットワークによって落ちる可能性のある不安定なテストは削ってしまっても良さそうに思います。 しかし、空文字自体がそもそもドメインとして成立しないため簡単に削ってよいのか判断が難しいところです。

案2. 空文字の場合はアプリ側で早期リターンする

DNS に問い合わせる前に空文字を弾けばネットワーク差分は発生しないのでアプリ側で制御する方法です。

   def valid_domain?(domain)
      return false if domain.blank? #Rails を使っている場合

      Resolv.getaddress(domain)
      true
    rescue Resolv::ResolvError
      false
    end
案3. Resolvをテストでstub 化する

これは自分では思いつかなかったのですが、chatGPTに他に解決方法を尋ねてみたところ、実DNSへの問い合わせを避けてスタブを使用する方法があるようです。

以下はMinitestのstubを使うことで、特定のオブジェクトのメソッドを他の処理で置き換える方法です。

getaddressを直接置き換えてしまうので、テストとして意味があるかは少し判断が難しいところではあります。

Resolv.stub(:getaddress, ->(*) { raise Resolv::ResolvError }) do 
  assert_not Checker.valid_domain?('')
end

※ この他にもネットワーク設定側で制御できる方法もあるかもですが、そこまでは調べられず…


この問題はそもそも起きているのが自分だけというのもあり一旦検討となり、Issueだけ軽く立てていたのですが、後日案2.のアプリ側で早期リターンで対応され自分の環境でもエラーが起きなくなりました🎉

参考資料

library resolv (Ruby 4.0 リファレンスマニュアル)

ruby/lib/resolv.rb at fcf3939780972d587b18afc26c4abd2da2c0b7ec · ruby/ruby · GitHub

class Thread (Ruby 4.0 リファレンスマニュアル)

スレッド (Ruby 4.0 リファレンスマニュアル)

「/etc/resolv.conf」ファイル - Linux技術者認定 LinuC | LPI-Japan

scutil(8) osx man page | unix.com

ASCII.jp:知ってトクするOS Xのコマンド(1) (1/2)

GitHub - apple-oss-distributions/mDNSResponder · GitHub

mdnsresponder(8) osx man page | unix.com

/etc/resolv.conf について #Linux - Qiita

/etc/resolv.confとは何者?

resolv.confのsearchとdomain - Lism.in * blog - nekoya (id:studio-m)

/etc/hosts と /etc/resolv.conf #dns - Qiita

https://docs.seattlerb.org/minitest/Object.html#method-i-stub

hosts, resolv.conf, nsswitch.conf - ご先祖様はきっと農民。

https://toshiocp.com/entry/2022/10/10/183002

【Ruby】Minitestでスタブを使う(stub/stub_any_instance) #Rails - Qiita

https://www.rfc-editor.org/rfc/rfc2606.html

感想

今回遭遇した問題は特定のネットワーク環境でしか起きない落とし穴っぽい内容(Discordを遡って検索してみたところ、過去に2名くらい同じような人がいたっぽい)のため、かなりニッチであまり役にたつ記事ではないかもしれませんが、個人的には調べていてとても面白かったです。

Rubyの標準ライブラリを通して「クライアント側でDNS解決がどう行われているのか」の理解を深める良い機会にもなりました。

また、ネットワークが絡むテストは不安定になりやすい(環境に依存する)というのも実感できたので、自分が実装する機会があれば気をつけたいと思いました。

そして、ブログへのアウトプットがだいぶ久々になってしまった(昨年のアドベントカレンダーぶり……)のですが、書きたいことがたくさんあり、3本ほどストック(そのうち2本は5割くらいの完成度ですが)ができました🙌

最近はアウトプットをおろそかにしていたのですが、アドベントカレンダーのおかげでアウトプットの楽しさを思い出したので、改めて頑張ろうと思いました!

明日のアドカレ

読んでくださりありがとうございました!

明日のフィヨルドブートキャンプのアドベントカレンダー2025は、mousu-aさんの予定です!

フィヨルドブートキャンプのアドベントカレンダー2025はこの後も素敵な記事がたくさんアップされて来ますのでお楽しみに!

輪読会で出会ったGit Bisectを理解したい~輪読会の良さみを添えて~

フィヨルドブートキャンプ Part 2 Advent Calendar 202420日目の記事です。

🎅 フィヨルドブートキャンプ Part 1 Advent Calendar 2024

🎄 フィヨルドブートキャンプ Part 2 Advent Calendar 2024

昨日の記事は以下になります。

part1 : f-kt6gさんのMacBookを使ってネットワークのパケットキャプチャを行ってみた

  • ご自身の経験とFBCで学んだ知識を活かした記事でとても勉強になりました!図も分かりやすい!

part2 : hagiさんのFBCの学習の振り返り

  • 自分も同じくらいの時期に学習を開始したのですが、進まれるスピードが速くとてもすごいなーと思っておりました!毎日欠かさず日報を書かれていて尊敬します。

はじめに

私はフィヨルドブートキャンプ(以下、FBC)内で開催されている3つの輪読会に参加しています。

  • 現場で使える Ruby on Rails 5速習実践ガイド(現場Rails)
  • マスタリングTCP/IP - 入門編 -
  • Good Code, Bad Code ~持続可能な開発のためのソフトウェアエンジニア的思考(現在、5人で共同主催中💪)

FBCへの参加を決めた理由の一つに、コミュニティ活動が活発であることがあり、できる限り月一で開催されるミートアップや輪読会といった定期的なイベントに積極的に参加しています。

しかし、フルタイムでの仕事をしながら学習しているため、どうしてもカリキュラム外のインプットやアウトプットに十分な時間を割くことができず、気になる知識を深掘りする余裕が持てていないのが現状です。

そんな中、アドベントカレンダーに参加する機会をいただきましたので、この記事では現場Rails輪読会で出会い、興味を持ったgit bisectというコマンドについて、実際に手を動かしながら理解を深めていきたいと思います。

Git Bisect

「Git Bisect」は、二分探索(バイナリサーチ)を利用して過去のコミットを探索できるツールです。

このツールを使うことで、過去のコミット履歴の中から、どの時点のコミットでバグが発生したかを効率的に調査できるようになります。

二分探索をざっくり理解する

二分探索はソートされた配列などに用いられる探索アルゴリズムです。

配列の中から検索値を探索する際、その配列の最小値と最大値を探し、中央値を取得します。

検索したい値が中央値より小さい(左)か、大きい(右)かを判断し、範囲を半分に絞りながら目的の値を探索していきます。

より具体的に理解するために0~9の合計10個の値が格納された配列があるとして、この中から3という値を探索していくイメージ図を作成してみました。

この配列では最小値が0、最大値が9となるため、中央値は4になります。

中央値(4)は検索値(3)より大きいため、中央値より大きい値は探索から外します。

残った範囲の最小値(0)と最大値(4)でまた中央値を求めます。

中央値(2)は検索値(3)より小さいため、中央値より小さい値をさらに探索から外します。

最後に残った範囲の最小値(2)と最大値(4)で求めた中央値は3となり、検索値と一致したため探索終了となります。

上記は単純な数値の探索ですが、Git Bisectで探したいものはバグを含んだコミットです。

2分探索をどのように活かしながらコミットを探していくかを、実際に動かしてみながら確認していきます。

Git Bisectを動かしてみる

準備

エラーの発生するコミットを含んだリポジトリを作成しました。 コードの中身は意味のないものですが、テストが落ちるようになっています。

github.com

  • sample_code.rb
def hello
  a_num = 5
  b_num = 2
  if a_num + b_num == 5
    p "こんにちは"
  end
end

def goodbye
  c_num = 3
  d_num = 2
  if c_num + d_num == 5
    p "こんばんは"
  end
end
  • sample_test.rb
require 'minitest/autorun'
require './sample_code'

class SampleCodeTest < Minitest::Test
  def test_hello
    assert_equal "こんにちは", hello
  end

  def test_goodbye
    assert_equal "こんばんは", goodbye
  end
end

現状でテストを実行すると以下のようにエラーが表示されます。

test_helloというメソッドでエラーが起きているのが分かります。

探索の開始

開始コマンドを実行

探索を開始するにはgit bisect startコマンドを使用します。

探索範囲を設定

git bisect startを実行したら、バグが混じったコミットを特定するために、探索する範囲を設定します。

  • git bisect badは現在バグが起きている(問題がある)コミットを設定します。二分探索の範囲の終端となります。

  • git bisect goodはバグが発生する前(問題がない)のコミットを設定します。二分探索の範囲の始端となります。

コマンドをまとめると以下になります。

$ git bisect start 
$ git bisect bad  <現時点で把握しているバグの入ったコミットハッシュ/タグなど> 
$ git bisect good  <現時点で把握しているバグの入っていないコミットハッシュ/タグなど>

実行開始と範囲設定はワンライナーで実行することも可能です。

git bisect start bad  <現時点で把握しているバグの入ったコミットハッシュ/タグなど>  good  <現時点で把握しているバグの入っていないコミットハッシュ/タグなど> 

実際にリポジトリのコミット履歴を用いて範囲を設定していきます(画像は見やすいように番号を振っています)。

コミットメッセージにも書いてある通り、⑦09a1d50のコミットにバグが仕込んでありますが、 最新のコミットである⑩の時点でバグに気づいたという想定で進めていきます。

まず、現在バグが起きている地点(範囲の終端)を設定します。

test_helloというテストが落ちていることから、helloメソッドでエラーが起きていると想定します。

最新のコミットである⑩はgoodbyeメソッドに関する追加になるため、goodbyeに関するコミットは関係ないと仮定して、⑨を最新のバグが起きているコミットとして設定します。

次に、この時点ではバグが起きていなかったと考えられる地点(範囲の始端)を設定します。

先頭のコミットから見ていくと⓪はReadme、①は空の新規ファイルを追加しただけのコミットになります。

この時点ではバグは起きていないものの、テストを記載する前ということもあり、今回の影響範囲には含まれないと想定し範囲からは除外します。 その次の②ではテストを新規追加しています。今回はテストを追加した時点でバグが起きていたかもしれないと仮定し、②を始端と設定します。

これで、探索範囲が決定しました。

図にすると以下のようなイメージになります。

準備が整ったので実際に探索を行っていきます。

探索はコミットを確認しながらgoodbadか設定して手動でチェックする方法と、探索とテストを自動実行させる方法の2パターンがあります。

手動でチェックする方法

まず、以下のコマンドを実行して探索を開始します。

$ git bisect start 
$ git bisect bad  f0a6d92(⑨のコミット)
$ git bisect good  78425f8(②のコミット)

badgoodを設定したところ、②と⑨の中央値である⑤ (2 + 9 / 2)のコミットにチェックアウトされます。

git log --onelineを見てみると、HEADが⑤の時点に移動されているのがわかります。

⑤に移動したら、テストを行います。

テストが成功したらバグは⑤の後から発生し 逆に、テストが失敗したらバグは⑤より前から存在しているという事になります。

テストを実行してみると、成功することが分かりました。

テスト結果から、このコミットにはgoodを設定します。

goodを設定することで、このコミット以前のコミットは範囲から除外され⑤が範囲の始端となります。

git bisect goodを実行すると、次のコミットに移動します。

例のバグが混入した⑦ (5 + 9 / 2)のコミットに移動となりました。

ここでテストを実行してみると、もちろんテストは落ちます。

このコードはbadとなるので、git bisect badを実行します。

ここで終わりではなく、終端となった⑦と現在の始端である⑤の間の⑥のコミットに移動します。 この結果次第で、バグが混入した時点が決定になります。

テストを実行すると、テストは成功することがわかりました。

git bisect goodを設定するとコミット⑦が最初にバグが混入されたコミットである、というメッセージが表示されます。

これで探索は終了となりました。

エラーが起きた時点のコミットを特定できたので、該当のコミットのソースをより詳細を調査したり、rebase -iなどを用いながら修正をしたりすることが可能になりました。

探索が完了しgit bisectを実行する前に戻りたい場合はgit bisect resetを実行します。

無事、探索前のコミット地点である⑩にHEADが戻りました。

その他の機能

goodbadを判定していくにあたり、自分が何をどう設定したかわからなくなりそうですが、 git bisect logを実行することで自分が今まで設定ログを見ることができます。

また現在の探索範囲を表示してくれるgit bisect visualizeというコマンドもあります。

自動で探索

上記の探索を自動で行うコマンドもあります。

探索範囲を指定後、git bisect run <テストスクリプトの実行>を実行することで、 先ほど手動で行った流れ(テストの実行→コミットの移動)を自動で行ってくれます。

コマンドを実行すると、以下のようなログが表示された後に該当のコミット⑦にたどり着きます。

$ git bisect run ruby sample_test.rb
running 'ruby' 'sample_test.rb'
Run options: --seed 54666

# Running:

"こんにちは"
.

Finished in 0.000372s, 2688.1720 runs/s, 2688.1720 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Bisecting: 1 revision left to test after this (roughly 1 step)
[09a1d5037241886b6dca8ad7049140374d529ae6] fix: 変数aをa_numに変更(ここでバグ混入)
running 'ruby' 'sample_test.rb'
Run options: --seed 65091

# Running:

F

Failure:
SampleCodeTest#test_sample_code [sample_test.rb:8]:
Expected: "こんにちは"
  Actual: nil

bin/rails test sample_test.rb:7

Finished in 0.000428s, 2336.4486 runs/s, 2336.4486 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[42ac7fd58ccce402b34fe9a51b764687c0655f8c] fix: a + b の結果で分岐する
running 'ruby' 'sample_test.rb'
Run options: --seed 49017

# Running:

"こんにちは"
.

Finished in 0.000319s, 3134.7964 runs/s, 3134.7964 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
09a1d5037241886b6dca8ad7049140374d529ae6 is the first bad commit
commit 09a1d5037241886b6dca8ad7049140374d529ae6

    fix: 変数aをa_numに変更(ここでバグ混入)

 sample_code.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

テストをちゃんと書いておくことで、より効率的にバグコミットを探すことができることが分かりました。

まとめ

  • git bisect startで探索を開始し、git bisect goodで探索の始端、git bisect badで探索の終端をセットする
  • 上記を設定すると範囲の真ん中のコミットに自動で飛ぶので、手動または自動で探索を行う
  • git bisect loggit bisect visualizeなど便利なコマンドがたくさん用意されている
  • テストを書いておくことでより効率的にバグを探索できる

参考記事

git-bisect – Git コマンドリファレンス(日本語版)

git-bisect(1)

git bisectで「誰だよ!バグコミット仕込んだの!」を解決する #Git - Qiita

git bisect で問題箇所を特定する #Git - Qiita

git bisectで「いつの間にか壊れていた」を探して直す(Gitの小ネタおれおれAdvent Calendar 2022 – 24日目) | Ginpen.com

(余談)輪読会の良さみ

私はFBCに入って初めて輪読会というものに参加しました。輪読会に参加する良さは以下だと考えています。

  • 自分にない視点を得ることができる

    • 一人で読んでいたらさっと読み飛ばしてしまうようなところも、参加者分のアンテナを張りながら読むことができるので、より深く本の内容を理解することができる。
      • 今回調べたgit bisectもこのコマンドなんですかね?!と輪読会中に話し合ったおかげで興味を持ち、今回の記事を書くに至りました。
    • 輪読中に分からないことが出てきた際も、参加者の知識で解決することができたり、できなくても関連した知識を共有したり、毎回新しいことを知ることができる。
      • 調べた結果分からないままだとしても、自分の中に一度調べたというインデックスが残る。
  • 繋がりを作れる

    • 人それぞれ学習スタイルが違う中で、定期的に集まって話せる人がいるだけでモチベーションがかなり違うと感じています。
      • 課題で詰まっている時なども、輪読会中の雑談に救われることが多々あります
      • 日々忙しくて参加できないということもあると思いますが、興味があるけど参加できていない、、という状態だとしたら(私も最初はかなり勇気がいりました)、ぜひ一度飛び込んでみるのをお勧めします!
      • 定期的なイベントの場合、途中参加はハードルが高く感じてしまいますが、単発でも途中で行けなくなってもOKな輪読会がほとんどなので、いつからでも遅くないと思います!
      • FBCの漬け水に浸かっている人はみんなとても優しい人ばかりなので、飛び込んでみてください。
  • 積読解消

    • 輪読会が5年寝かしていたマスタリングTCP/IPを読むきっかけになりました。

終わりに

自分の知らなかったgitの機能を知ることができてとても勉強になりました。

今回はサンプルリポジトリで動作を確認するだけしかできませんでしたが、課題に取り組む際などに使う機会があれば積極的に使ってみようと思います。

輪読会をはじめ、FBCに入ってから日々自分の知らない知識に出会いながら楽しく学習を進められています。

1日でも早く卒業して就職したい気持ちもありますが、楽しむ気持ちを忘れずに学習を続けていきたいと思います!

Rail7のstatus: :see_other、status: :unprocessable_entityについてメモ

はじめに

Rails7でscaffoldした時にdeleteやupdateのredirect_toについてくるstatus: :see_otherstatus: :unprocessable_entity

前職で携わっていたRails5やRails6のプロジェクトでは見たことがなかったので調べてみた。

deleteにデフォルトでついている

  # DELETE /users/1
  def destroy
    @user.destroy!
    redirect_to users_url, notice: 'User was successfully destroyed.', status: :see_other
  end

なんなのか

status: :see_otherはHTTPステータスコード303に対応している。

新しくアップロードされたリソースではなく別ページにリンクする時にこのステータスを指定しないと、 一部のブラウザなどでは元のリクエストメソッドを使用してリダイレクトしようとするため、 二重 DELETE などの望ましくない動作が発生する可能性があるらしい。

Rails 7でデフォルトになったturbo-railsに対応するために必要らしく、 railsガイドでもこのステータスを指定することを推奨している。

※ button_to の場合は status: :see_other を付けなくても正常に動作する。

updateの方は?

  # PATCH/PUT /users/1
  def update
    if @user.update(user_params)
      redirect_to @user, notice: 'User was successfully updated.', status: :see_other
    else
      render :edit, status: :unprocessable_entity
    end
  end

なんなのか

status: :unprocessable_entityHTTPステータスコード422に対応している。

サーバーにリクエストが到達はできているけど、送られてきたデータが原因でリクエストを処理ができなかった時などに返ってくる(バリデーションエラーなど)。

このステータスを明示的に返してあげないとエラーメッセージが表示されない。

そもそもなんでついているのか(これから追記予定)

Rails7から標準になったgem 'turbo-rails'に対応させるために上記のようなステータスをちゃんと返す必要があるらしい。

参考

ActionController::Redirecting

Getting Started with Rails — Ruby on Rails Guides

422 Unprocessable Entity - HTTP | MDN

レイアウトとレンダリング - Railsガイド

Turbo Handbook

Rails7+Mysql8.0環境をDockerで構築したらOperation not permitted (ThreadError)

はじめに

エンジニア復職を目指しキャッチアップでRails7環境を作成したときに詰まったところをメモ。

起きたこと

参考記事を見ながら以下ファイルの生成&編集

docker-compose.yml, Dockerfile, Gemfile, Gemfile.lock, entrypoint.sh

そしてdocker-compose up --buildを実行!

・・したところ、下記エラーが起きた。

/usr/local/lib/ruby/3.2.0/open3.rb:223:in `detach': can't create Thread: Operation not permitted (ThreadError)

エラーファイルを追って見るとDockerfileのbundle installで起きているらしい。

なんじゃいと思いエラーを調べてみたもののいまいち解決方法が分からなかったが、以下の記事を発見。

RabbitMQ & Docker: Failed to create thread: Operation not permitted (1) - Stack Overflow

I add the same issue with Docker 18.03 , solved with docker v20.10

自分のDocker見てみると、Docker version 19.03.13。古っ!

今までは会社のPCを使っていたから、自分のPCの開発環境は3年前のもので止まっていた・・。

しかもDocker for Desktopからアップデートしようとしたところ最新の更新を取得できないというエラーが起きた。

とりあえず、まずはDockerを再インストールしてそこからもう一回様子を見てみる。

やったこと

brew update

今入っているDockerはDocker Desktopだったので、 今後の管理を兼ねてbrew installでDockerを入れられるようにまずbrew自体を更新

brew upgrade docker で失敗したので brew update しました。 #homebrew - Qiita

dockerアンインストール

MacでDockerを完全にアンインストールする3つの方法 - Dr.Buho

dockerインストール

brew install docker --caskを実行。

以下記事とおなじエラーが起きたので一度brew remove docker も実行した

Mac 向けDocker をHomebrewでインストールしようとしたらつまづいた話 #Docker - Qiita

再度docker-compose up --buildを実行

無事ビルド成功🎉

その後は参考記事の手順通りでRails画面まで到達。

感想

  • 後から色々調べたらおなじようなエラーが起きている場合、Dockerfileのbundle installのバージョンを指定したらよかっただけなのかも・・?まあでもこれからまた色々やるにあたりアップデートするに越したことはないのでよかった。

Docker環境でCould not find 'bundler'のエラーが出た時の対処法 | book-reviews.blog

  • このエラーを1個1個解決していく感じ、やっぱり楽しいなあと思った

参考

開発環境作成

【Docker】Rails 7+MySQLのDocker環境を構築する

DockerでRails7+Mysql8.0環境を構築してRspecを書くまで #Docker - Qiita

【初心者向け】Rails 7+MySQL 8.0の開発環境をDocker composeで作る方法 | Muscle Coding

Docker周り

HomebrewでDocker Desktopを再インストールするときに注意すること - とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

MacにHomebrewでDockerをインストールする – インターステラ株式会社 技術ブログ

【Mac M1】Dockerのインストール方法

エラー調査

ThreadError can't create Thread: Resource temporarily unavailable · Issue #4353 · rubygems/rubygems · GitHub

Object-Oriented Conference2020に参加してきた。以下レポっす。

  • 2020/02/16に御茶ノ水女子大で行われたオブジェクト指向がテーマのカンファレンスに参加してきたので、レポ投下。
  • Object-Oriented Conference概要。
  • 敬称略です。
  • 駆け足でメモしたところがかなりあるので、間違っているところ、あいまいな部分かなりあると思います。
  • 詳細、正確な情報はYOUTUBEアーカイブをご覧ください!(自分も見返さなければ)(いつまであるかは不明)

基調講演

keynote: Object-Oriented Diversity

登壇者

成瀬 允宣(@nrslib)

概要

fortee.jp

スライド

speakerdeck.com

レポート

あなたのオブジェクト指向はなんですか?

『多様性』をうまく扱うために、ポリモーフィズム多態性)がある。

ポリモーフィズムは前後関係による文脈が大事、
  • 前後関係をうまく使うのがポリモーフィズム
  • 前後関係 は英語で『コンテキスト』。
  • 一連の中のコンテキストを恐れずに使ってほしい。
  • コンテキストを理解することが大切、多様性をうまく扱う。
  • コンテキストを理解すると、素直なプログラムが書ける。
ポリモーフィズムを恐れない為のアドバイス
  • 対話するとき
    • 例えば、カンファレンスで質疑応答するとき
      • もしかして、質問する人がつよつよにみえてる??まさかりコワイよね。
        • 質問が出るということは自己表現したいということ。
        • 好奇心旺盛になっている子供と一緒。そう思うと見え方が変わってくる。
  • コンテキスト、来歴を理解してあげましょう。

所感

  • ホールAでの発表だったが、すでに満席でYoutube配信をホールBでみた。
  • オブジェクト指向に関わらず、コンテキスト(前後関係)を理解するのはとても大切。来歴を理解することで、争いや誤解が生まれなくなる(はず)。生まれたとしても、建設的に話ができるのでは。
  • 質問される側の心構え的な話がでたが、質問する側も相手のコンテキストを理解した上で質問するの大切、とおもった。

セッション

DDDはオブジェクト指向を利用してどのようにメンテナブルなコードを書くか

登壇者

松岡(@little_hand_s)

概要

fortee.jp

スライド

little-hands.hatenablog.com

レポート

DDDとオブジェクト指向の関係

DDD
  • ソフトウェア開発志向の1つ。
    • 適用する対象の何らかの問題を解決するために導入される。
  • Domain Language(https://domainlanguage.com/ddd/reference/)
    • 本より読みやすいのでおすすめ。
  • モデリングから劇的な利益を得られたものからエッセンスを抽出する。
モデルとは何か?
  • まず、DDDでの『モデル定義』をしよう。
    • モデルとは問題解決のために物事の側面を抽象化したもの。
      • ドメインモデル
        • 現実世界をも問題を解決するモデル。
      • データモデル
        • DBに何かを永続化するモデル。
  • 今回はドメインモデルの話。
抽象化について
  • たとえば、履歴書
    • 履歴書の要素として、写真を貼る場所、履歴を描く場所などがある。
    • 上記の目に見える要素以外にもたくさん要素がある。
      • 履歴書のメーカーや紙の素材、書いているときの気持ち・・・etc。
        • すべての要素をモデルに落とし込むことはできない!
    • 抽象化とは、要不要の判断をすること。
よいモデルとは
  • わかりやすいモデルが良いモデルか?
    • No。問題解決ができることモデルが良いモデル。
よくないモデルとは
  • 問題解決ができない。
  • 実例
    • 採用管理システムで考える
      • 書類選考、一時選考、最終選考を考慮したシステムを構築しました!
        • 書類の前に面接があるんですけど・・・。
        • 求人の関係ない採用もあるんですけど・・・。
      • 上記のような柔軟な対応がむずかしい、となるとどうなるか。
        • 運用でカバー!!!(技術の敗北)
          • カバーできるレベルなら問題ない。
            • 他のUXと競う場合は完全に不利になる。
  • DDDの実装パターンを的確に反映して、まったくバグがないシステムを実装したとしても、拡張性がなければ利益は生み出せない。
よいモデルを創るには
  • ドメインに詳しい人にしっかり話を聞く。
    • しっかり会話をして得た知見をモデルにフィードバックする。
    • モデルは最初から完成しない、変わっていくもの。
モデルとコードが乖離してしまうと・・・
  • モデルの更新をコードに正しく反映できているか分からなくなる。
  • 案件に途中から入る人はコードから読む。
    • コードからモデルが理解できない、モデルからコード理解できないとソフトウェアとしての価値が低い。
  • モデルがそのままコードになっているのが望ましい。
    • 「オブジェクト」でモデルを表現したい。
  • モデルを継続した変更に耐えうるようにするためには、拡張性の高い設計が必要。
軽量DDD
  • DDDの実装パターンだけ取り入れる手法に意味はあるか?
    • 意味はある!
    • 拡張性の高いベストプラクティスを取り入れるだけでも十分価値がある。
    • コードがぐちゃぐちゃすぎるとできない。コード品質を上げると、漸くモデリングする余地が生まれる。
ドメインモデリングの方法
  • ユースケース図、ドメインモデル図だけつくる。
  • ユーザとアプリケーションの、相互作用を定義する。
    • ユースケース
      • 一般的なUMLのものと同じ。
      • ユースケースを具現しないとモデル出来ない。
      • 解決したい課題が解決できない。
    • ドメインモデル図
      • クラス図の簡易版。みんなでホワイトボードでかきかきする。
      • 振る舞いはいらない。属性も代表的なものだけでOk。
      • ルール、属性、ドメイン知識を吹き替えで記述する。
      • 集約の範囲を明記する
        • 集約は決めておいたほうが
        • 集約 =「かならず守りたい、整合性を持ったオブジェクトのまとまり」
  • 実際のモデリング方法はホワイトボードで殴り書き。
コードへの落とし方
  • アーキテクチャは代表者がざっと決めて開発を進める。
  • プレゼンテーション層 ⇒ アプリケーション層 ⇒ ドメイン層(エンティティ)の順番。
  • まず動くものを作りましょう!ひどいつくりでもOK。
  • その後、リファクタリングしていく。
    • ドメインモデルにかかれてる制約がアプリケーション層に実装されていた ⇒ ドメイン層に移譲する。
    • 整合性が保たれるし、アプリケーションがシンプルになる。
  • レイヤーによって書くべきことが決める。
    • レビューコメント論争を生まない為に。
    • コード全体に規律を持たせることができる
  • お手本コードを作る。
    • Application,Entity,Repository。
    • ドメインモデル図をもとに実装する。
  • 本を読む。
  • 手を動かしてみて、どう書き分ければいいんだっけ??と模索してみる。
    • 模索することで、モデリングの必要性を感じやすい。
    • どういうコードに落とし込まれるかをイメージできた方がいい。
    • DDDのコーディング、モデリングともに一発では難しい。
    • コーディング、モデルのどっちかだけうまくなることない。

質問

※ 質問はslidoより転載。

Q.既存のMVCベースのフレームワークを利用していると、オニオンアーキテクチャやクリーンアーキテクチャの適用が難しいなと感じています。MVCにおける○○はどのレイヤーなのか?このレイヤーは○○を利用すべきなのか、それとも別のクラスにすべきか?等で手が止まります。どのような手順で適用していくと良いのでしょうか?
  • A.Railsとかの場合、変にMVCを引きずるとぐちゃぐちゃになる。
    • オニオンで行くならオニオンで行く!と決めるほうがいい。
Q.集約単位の見極めが難しいのでは?こつはありますか?
  • A.本にもあるが下記2つ。
    • トランザクション範囲が大きくなってしまっているか?
    • 整合性保ちたい範囲はどこか?を決める
Q.モデルの新規作成や改善にあたっていきなりコードから入る人がいます。個人的にはいきなりコードに入る前にもう少し手前でたくさん試せればいいなと思うのですが、その方法についてアイデアはありますでしょうか?
  • A.価値を感じるまでが難しい。。
    • 話ながらいきなりボードに書き出す手法がある。(目の前で描かれると無視できない。)
Q.軽量DDDに価値がないとは思わないのですが、問題は軽量DDDを行って手元でコードを書いて満足してしまう、この状態でDDDをやってると思ってしまう人がいることだと思います。お話されたようなモデリングに向かうアプローチがあってこそのDDDだと思うのですが、そこに意識が無い人への対応など、何かお考えなどありますでしょうか。
  • A.自分が作ったシステムが本当に役立ってるか?というところに意識を向けることが大事。
Q.CQRS などの文脈で Query はレイヤーを両略してよいだとか、ORM を使ったシンプルな実装でよいという意見をよく見かけるのですが、データの解析やビジュアライゼーションがメインのアプリケーションで、複雑な Query ロジックをドメイン層で表現するのはありですか?(ドメインエキスパートはデータアナリストです)
  • A.クエリに寄せたほうがいい。更新系と参照系でオブジェクトを分ける。
    • 更新はオブジェクトの切り方、集計とかビジュアライズは参照の用途で違うので知識が混ざらないようにしたい。
Q.お手本のコードは詳しいひとじゃないと書けないですか?
  • A.やってみたいひと、強い意志がある人で進めればいい。
Q.軽量DDDはコードからDDDを導入する、とお話しがありました。例えば、ドメインエキスパートがもういない、すでに仕様をわかるひとがいない、レガシーなコードで保守だけでなく機能追加もある…みたいな案件で、それぞれの課題をクリアーにするために、軽量DDDは使えるのではないか、と感じましたが、松岡さん的にはアリでしょうか?
  • A.「コード汚い」という問題と、「仕様がわからない」という問題は違う。
    • 仕様がわからない・・「ドメインモデル図」を仕様理解のためにつかえばいい。
Q.golangでDDDはできますか?
  • A.できる!

所感

  • 技術書典で本を絶対買います!
  • 重要なことは「問題解決できているか」。DDDを導入することではない。
  • 問題の切り分けが上手になりたいなあ。
  • DDDは「やってみたいひと、強い意志がある人で進めればいい」という言葉に感銘を受けた。 自分には技術が足りないから・・とかで逃げない。責任をもって進もう。と思った。

オブジェクト IPPON グランプリ

登壇者

  • パネリスト Chatwork株式会社 加藤 潤一 (@j5ik2o) 株式会社バリューソース 神崎 善司(@zenzengood) 弁護士ドットコム株式会社 天重 誠二(@tenjuu99) 株式会社オージス総研 原田 巌(@iwaoRd)
  • モデレータ 株式会社チェンジビジョン 平鍋 健児(@hiranabe)

概要

fortee.jp

スライド

  • 見つけられませんでした。(見つけ次第貼り付けます)

レポート

UMLは何の略ですか?

  • 回答無し。

なぜオブジェクト指向

  • 考えるツール(神崎)

オブジェクト指向はClass?Message?

  • Scalaやってるとどっちもある(神崎)
    • プロログ以外の物はすべて入ってる。
    • 向いてるところで使えばいいと思っている。
    • 考えるツールと言っていたが、関数型も好き。自分で書くときは関数。対象をとらえるときはオブジェクト指向
    • 最近はテクニカルなものに興味なくなった
  • ユーザと話した時に、何を考えているんだろう?というときにオブジェクト指向で考えている。 どっか分析をしていて、Entityとしてとらえてる。(原田)
  • オブジェクト指向は自分の考え方に合うと感じる。(平鍋)
    • いまや分析論とまでになった。対象カテゴリを決める、知る、概念となってしまった。
      • オブジェクト指向はプログラムから生まれたと言われている。
        • ヒープ上にずっと残っているもの=オブジェクトだった。
  • 両方捉えられるものだと思っている。(天重)
    • アジャイルの言語でものをつくるとき、はやく作らなきゃ!ユーザと認識合わせなきゃ!同プログラムにつたえよう!設計⇒分析⇒要求というライフサイクルを回すことも近い。
  • Complexな状況に適応するため。(加藤)
    • 環境の変化に耐えられる。ひっくるめてこれにいきつく。
  • 存在たら占める何かを合意する。(原田)
    • 属性を切っていった後に見える本質。

DDDってオブジェクト指向?

オブジェクト指向GUI か?(天重)

  • アラン・ケイオブジェクト指向を産み出したと同時にGUIも生み出されたが、イコールになりうるか?(天重)
    • ~1995 YES 1995~ NO。(神崎)
      • まともなWindows機が出てくるまではそうだったが、今は違う。
    • モデルを渡すのではなく、モデリングをみんなでする。(原田)
      • GUIであろうが、ロジックだろうが、目の前で会話をかくだけで、モデルを残せる。お客さんはモデルに影響がある。 現場で6年間やって漸く理解して貰えてきた。モデルアーティファクトモデリングみんあでやった
    • UserAcount/rename("kato")(加藤)
    • イメージを捜査してシンボルに至るbyアラン・ケイ(天重)
    • アジャイルにつぶされた(平鍋)

コードがあればモデリングはいらない?

  • コードの量が多いのはしんどい。(平鍋)
  • エディターがどんなになっても便利になっても、10万行もあればわからないし、理解できない。(神崎)
  • マギシステムの付箋(エヴァ)。(原田)
    • マギシステムになにかあった時に、システムの裏に付箋が貼ってあって調整する。
      • こういう存在であってほしい。コードがあればいいということはない。
        • 設計意図がつたわる。モデルがあれば理解出来るというところを大切にしたい。
  • モデリングは仮説⇒コード・テストは実証。モデリングは図示、コード・テストは詳細。(加藤)
    • 二重管理は大変だがツールで管理すればいい。関連づいていないといけない。
      • 複数モデルは書く?(平鍋)
  • モデルはシミュレーション、コードは制約(天重)

好きなモデリングツールは?

  • パワポ(神崎)
    • マクロが書けるのでモデリングツールになる。最近はもっぱらパワポ
    • 要件定義とやる人と絡む際、相手の敷居が下がる。
  • ことば(天重)
  • miro.com or ふせん(加藤)
  • Aster(主に手書き)。(原田)

所感

  • OOCのデザインをみて、Ipponグランプリを思いついたらしい。たしかに!
  • そういえば、OOCのデザインはなんで黄色と黒なんだろう。
  • パネリストの方々は歴史というか原案てきな部分をしっかり網羅している。そこら辺の部分の理解の甘さを痛感した。本や原著をしっかり読んでおきたいと思った。
  • miro.com、使ってみたい。
    • 「モデルを渡すのではなく、モデリングをみんなでする」はとても大切だと感じた。DX。

概念投影によるオブジェクト指向設計の考え方とその方法

登壇者

hirodragon(@hirodragon112)

概要

fortee.jp

スライド

speakerdeck.com

レポート

前提

  • プログラミングの基礎があること。
  • そのうえで、設計をやってみたい。何からやってみてもわからない。設計活動の迷いとの闘いをしている人向け。
  • 何が正解かの捉え方の説明。
  • 設計活動の考え方。
  • DDDとか具体的な手法概念の説明しない。
  • Before Principal、Before pattern、Before Paradigm

設計を難しく感じる理由

  • 正解がわからない、何から始めればいいか分からない、GOALがわからない・・。
  • 設計が進んでいる道が分からない。
  • スタートラインがわからない
    • DB、画面設計書?クラス図?
  • ゴールがわからない。
    • ゴールなんてない!
  • 何かを覚えてやる、暗記でOK? 設計は暗記じゃない、論理が組みたっていないとできない。
  • 全体像を知っていると認識が違う。
    • 迷いはミスにつながる。
    • 自分の中に指針を持って設計すること。

アプリケーション開発とは

  • 現実世界の活動代行
    • 現実世界は意様々な活動を無数のアプリケーションが支えてる
    • 小人が中で動いているようだが、実際に小人はいない。
    • やりたいことをイメージすることが大事。ソフトウェア化の前提をクリアにしないといけない。

設計とは

  • 4w1h
    • What・・・小人がいる世界を作る
    • Who・・・エンジニア
    • Where ・・・コンピューターの中に
    • Why・・・代行させたい
    • Howが一番重要。
  • システム化したい領域を投影する。この無の世界に現実世界を代行してくれる新たな世界を作り上げる。
  • 世界を創世している、くらいのつもりで取り組む。エンジニアの力で、無から有へ。
  • ただし、コンピューターは現実世界を知らない。
    • 与えられた武器はプログラミング言語
    • ここに現実世界の「営み」を代行してくれる小さな世界を作り上げる
  • じゃあ、どうやって世界をつくるのか?
    • 概念投影。
      • コンピューターは現実世界を知らない
        • 設計をせずともプログラムは動く。簡単に作れる。
        • ただし、ほんとにコンピュータも理解してますか?
          • 例えば、コレクションを受け取って性別が"man"だったら配列に加え、男性だけのリストを返すとする。
            • 実際はコンピューターは不明な何かを受け取って、不明なプロパティが文字列の中と比較し、配列にいれて、出来た配列を返しているだけ。manとか知らん。
            • 無の世界に持ち込まれたのはプログラミング言語の作る世界のみ。
        • 人間は文字(man)を見て、自分の世界の概念と照らし合わせることにより無意識に認知ができる
        • コンピューターはプログラミング言語が成約した概念のみしか認知できない。
        • このようなギャップのことを『セマンティックギャップ』という。
          • 「認識が当たり前ではないと意図的に考えない」と、セマンティックギャップが認識できない。

概念を投影するとは?

  • 限られた武器でコンピューターに仮想的な現実世界を教えて存在させる必要がある。
  • 存在 = 実在ではなく『認識』できること。
  • 認識できる=『概念が定義されていること』。
    • 例) ゴルフクラブがある。これは存在か? 幼稚園児から見るとそれはただの棒。概念がない。 おじさんから見るとゴルフクラブ。概念を知っているから。
  • 概念を教えて仮想的な現実世界を作る ⇒ 概念を投影するということ。
  • 与えられた武器はプログラミング言語

公理と定義

  • 公理
    • 無条件で正しいと定められているもの。正しいという証明が不要なもの。
    • string、int、bool、dateなど言語が用意する型やClassも公理。言語が提供しているものは公理。
  • 定義

    • 公理をもとに新しく正しいと定めたもの。
    • ユーザー定義クラスなど。
  • 無の世界に投影させる際、公理の方のみ使用すると現実世界にある概念が存在しない世界となってしまう。

  • 定義は公理を使用して型を作るが、定義は人によって意味が異なる可能性がある。

Activity(営み活動)

  • 何かを売る・買うなどといった、アプリケーション有無に限らず存在する大前提。
  • Activityを表現するために必要な論理、ロジック。
  • アプリケーションを駆動するために必要だが、現実世界の営みに影響力を持たない。
  • 現実世界の営みを代行してくれる小さな世界を作り上げる

Domain Actibity

  • 例えば、「ホテルの予約」というような営みがあれば存在するもの。アプリがなくても別の媒体でできる。
  • ドメインモデリング
    • 必要な単語を洗い出す(用語集を作る)。小さな単位でまとめる。
    • 出てきたアクティビティに関するものをまとめる。
    • 作った単語集を少しステムに近づける 。
  • 概念投影
    • ドメインモデリングの対象者たちをアプリケーションに投影する。
    • アプリケーションが現実世界の知識をもつ (柔軟性をもつ)。

ApplicationLogic

  • Activityと混同しがち。ClassというよりInterface。

暗記しても意味ない。目的を実現するためのもの。

  • 手法や理論を用いる前にその活動が何を実現しようとしているのか指針を持つことが重要。
  • 概念の投影により、仮想世界に現実世界を存在させる概念投影指向という考え方。
  • 投影されたすべては単一で存在する概念。Atomicな存在まで概念を分析する。
    • オブジェクト指向ではClassとして表現される。
    • 単一債務、関心の分離 設計せず複数の債務監視を持った処理作成など。
    • 概念投影指向の概念はすでに単一で分離する必要がない。
    • 単一な概念を統合するとDomainとして表現が膨らむ。

表現としてのふくらみ

  • レイヤードアーキテクチャ

    • Port- Adaptor
      • Application Logicとして投影されている
  • 契約による設計

    • 関数シグネチャに型を指定そ、各振る舞いの事前条件や事後条件を制限する。
    • 契約したい概念はActivityとApplicationLogicとしてアプリケーションの世界で認識できるようになっている。
    • あとは契約を描くだけ。公理の方のみでは契約書けない。
  • DDD
    • Domain蒸留サイクルの中心に据えた設計方法。
    • 概念投影と相性良い。
    • Activityの洗い出しをするドメインモデリングをする際にできたモデルをアプリケーションに落とし込む。
    • ドメインがアプリに振り回されることはない戦略設計に基づいて、ドメインを蒸留していく。

所感

  • 主催者の方の話が聞けたのはあつい!
  • 内容えもい。我々は世界を作っている。そのくらいの気持ちが大切。
  • 概念の話、「散歩する侵略者たち」という映画を思い出した。
  • 最初のコンテキストの話にも通じるものがあった。
  • 公理と定義の考え方、何かを考えようと思ったときに一番最初に考えるための指針として持っていようと思った。

オブジェクトライフサイクルとメモリ管理を学ぼう

登壇者

ariaki(@ariaki4dev)

概要

fortee.jp

スライド

speakerdeck.com

レポート

  • オブジェクトはどこからどこへ行くのか。
  • 星の数ほど作り出されるオブジェクトにどんな意味があるのか?

メモリ管理

  • CPUの中にレジスタがあり、内部データを保管している(x64系は16個ある。64*16)
  • フロントサイドバスがメモリからCPUに転送する。
  • CPUの中でメモリとやりとりをするがかなり低速。x64系はメモリがキーとなっている。

  • New object(); されたとき何がおきるか

    • object生成⇒格納先候補(アドレス)⇒変換テーブル⇒実格納先。
    • new object();⇒ 論理アドレス ⇒ ページテーブル ⇒ 物理アドレス
    • 変換テーブルを参照する理由はいろいろあるが、セキュリティのためもある。
  • x64アーキテクチャでは?

    • OS <-> RAM間は64bit単位で読み書きされる。
    • 64bit中48bitがメモリアドレス。(最大256TB)
    • 48bitが5つの箱に入っており、それぞれのテーブルが次のテーブルの場所を差している。
    • 具体的なオフセットアドレスを差している。
    • MMU(メモリマネジメントユニット)がメモリのアドレスを割り当ててる。
    • アドレスが変換された対応表のTLBがある。
      • TLBがあれば、高速にアドレスを引き出せる。TLBがあるからこそ今日のCPUが速い。
        • ただし、あるせいで攻撃されることもある。
      • if文などの分岐の処理をCPUが先読み実行出来る。
        • ブランチ両方を先読み。
        • 実行して成功率が高くなければ成功率下がる。
        • 投機的実行。突き詰めればもっとはやい処理が書ける。
    • メモリ要求用に応じて4kb、2mb、1GBごとに区切られたページが確保される。
    • 仮想~物理の対応はページテーブルで管理。
    • 頻繁に使われるアドレスはTLBにキャッシュ。
    • ページテーブルはOSが定義しているが、動作はCPUの機構。

メモリアロケータ

  • プログラム ⇔ メモリアロケータ ⇔ OS-CPU
  • malloc()
    • 確保したいメモリサイズに応じて自動で最適なサイズを割り当てる。
  • メモリサイズごとに異なる領域を確保。
    • fastbins < 160
    • bins> 160
  • 大きなメモリはmmap命令。
  • freeしても内部的にはキャッシュしていることある。
  • chunk単位で確保。8byteでアラインメントされている。

アラインメント

  • アラインメントとは、メモリ使用教を キャッシュラインにあわせること。

      • short・・・2byte
      • int・・・4byte
      • float・・・4byte
      • double・・・8byte
      • char・・・1byte
  • アクセスに無駄がなく、高速に読み書きできる

  • CPUによってはアラインメント違反はエラーになる未使用領域が発生する。
  • アラインメント最適化
    • キャッシュライン+高級言語の仕様。
      • メモリがどのくらいひつようかは、言語仕様をを読み解かないといけない。

スタックとヒープ

  • スタック
    • 確保・解放が速い。
    • 寿命が短い。
    • サイズ小さい。
      • コールスタック
        • 関数様に確保されている。ローカルの変数等。
        • 関数ごとにスタックフレームという枠を作ってデータを入れている。
        • 積んでいることで、そのリターン後に自動で破棄される。
  • ヒープ

    • 確保・解放が遅い。
    • 自分で寿命を決められる。
    • サイズ大きい。
    • 参照型の実態を持つ。
    • 好きなだけ好きな時に割り当てられる。
    • 誰かが解放しないといけない。
  • CPUの中で処理できるデータは64bit(xbit)

  • メモリの読み書きを頻繁に行っている。
    • 高速に保つためCPU内でN次キャッシュをもつ。
    • アクセス速度はバスクロック/メモリクロックに依存する。
    • オブジェクトで管理されるメモリ領域はアラインメントされる。
    • 大きなオブジェクトはの確保は重い。

オブジェクトの正体

  • New object(); -> オブジェクトヘッダとオブジェクト実体ができる。
  • オブジェクトヘッダの言語ごとの実装
    • Javaについては割愛してしまいました・・(スライドを参照くださいませ)

    • PHP5

      • ZVAL構造体
      項目 内容
      refcount 参照カウンタ
      type 型を示す文字列
      is_ref 参照集合かどうかを判定するbool値
    • PHP7

      • ZVAL構造体
      項目 内容
      interned 文字列がインターン化された状態
      refcount 参照カウンタ
      is_ref 参照有無
      • interned(インターン化)

        • 同じ文字列を共通領域に保管し、個別にメモリを確保せず参照する。
        • 数値などの他の方は対象外。
        • どれくらいメモリを使ったか。
        • 最初から定数と持っているものはメモリをかくほせず、インターン文字列としている。それ以外は別途メモリが確保される。
        • 変数は実態は別にある。
          • 箱の中に文字列が入っている(実際のメモリ)を参照。
            • リンクが切れた(参照が終わった)場合hrefcountが0になる。
              • GCの対象になる。
              • ref_countは実際にデータを参照しているのが何個あるかを示す。
      • コピーオンライト

        • $ar2 = $ar1;の時点では$ar2のメモリは確保されない。
        • $ar2[0] = 1;の時点で値が複製(コピーオンライト)される。
        • 配列に値を一個ずつ突っ込むと、8個ごとにメモリを追加確保していることがわかる。
          • 8倍ごとにメモリが追加確保されている?
        $ar1= ['a','b','c'];
        $ar2 = $ar1;
        $ar2[0] = 1;
        unset($ar2);

GC(Garbage Collection)

  • GCとは

    • 不要になったメモリ領域を自動で回収する仕組み。
    • 今年60周年。
    • 主要な言語ほぼすべてに採用される。
    • メモリ使用量が一定の閾値を超えたら実行される。
    • メモリリークが発生する。
  • 代表的なアルゴリズム

    • 参照カウント・・・オブジェクトの参照数が0のもの。
    • マークアンドスイープ・・・未使用のオブジェクトをマークした後に削除。
  • 循環参照するとメモリが残り続ける。

    • unsetしたとしても、PHPが判定できない。
    • GCが発生するタイミングでクリーンされる。
  • PHPGC

    • 参照カウント方式。
    • GCコレクトサイクル。
    • unsertで即座にアクセスできなくなる。
    • オブジェクトが10000件達するまで削除されない。
    • 三色マーキング
      • 白色・・・GC前。
      • 灰色・・・到達した。
      • 黒色・・・到達済み。

まとめ

  • 設計だけじゃなく、プログラムの中の設計にも目を向けてみよう!

所感

  • がっつり低レイヤーの話!むずかしかったけど面白かった。
  • メモリ管理、普段考えられてなかったけど今後はしっかり考えてきたい。
  • 言語(同じ言語でもバージョンによって)によってメモリ管理の仕方が違うということがざっくり理解できた。これから使う言語(ruby)についても調べてみよう。

タイトル

オブジェクト指向システム開発

登壇者

株式会社メディアドゥ 濱口賢人(@kent_hamaguchi)

概要

fortee.jp

スライド

  • 見つけられませんでした。(見つけ次第貼り付けます)

レポート

まず、オブジェクトの定義をしよう!

  • システムそのものもオブジェクトである。
  • 設計は仮設提唱。
  • 苦しまない開発をしたい。

オブジェクトって何?モデル?

  • そもそもオブジェクトとは? ふるまい、構成要素、hogehoge?
  • 本セッションはでは、『唯一の振る舞いであり、それを支えるための洗練された構成をもつこと』と定義してみる。

各レイヤーのオブジェクト

  • 横と縦で考えてみる。

      • 組織観点
      • プロダクト観点
      • システム観点
      • インフラ観点
      • ソフトウェア観点
      • 組織観点 -> 組織論、人間論、コンベイ・・・。
  • 組織もオブジェクトなのでは。定義があって、インスタンスが立っているイメージ。

  • システムもオブジェクト。

各レイヤーをオブジェクトとして見てみる。

  • クリーンアーキテクチャー。
  • 上位レイヤーのオブジェクトは論理性が破綻しているととつくれない。
  • 成果物が目的に成り立たず、破綻する。
  • 仮説
    • 目的への達成方法を論理設計する。
      • ソフトウェア開発は論理的なコンピュータを扱う。
      • 故に論理的に成り立っているべきである
      • HOWTOからはいっていっても、結びつかないと意味がない。
  • 証明

    • 仮説を結果へ成果物を作成し実証する。
    • 論理的。筋的に成り立つものであれば実装できるべきである。
  • オブジェクトを支える仮説実証の流れをアジャイルスクラムで回していく。

    • コーディングに入った途端分からなくなる、ゴールを見失う。
    • 振る舞いはなんとか担保できても、内部構造が破綻する継続的な改善できない。
    • そもそも後から理解出来ない

これはゾウですか?

  • 組織のトップレベルで話されているが、セクションが分かれすぎてそれぞれの部署の話しかしてない。
  • 大きい組織でなればなるほど裁量が分かれていってしまう。
  • 全体を視座を上げる。
  • 事実に基づく信憑性のある仮説を立てて、結果を直視する。
  • 仮説たてながら事実に基づいた正しいふるまいのオブジェクトを蒸留する。

半面直感を大事にする

  • いろいろ考えても、結局机上の空論。
  • 直感を検証なしにこねるのはよくないが、素早く検証し事実か確かめることは大切。

オブジェクトを使う目的

  • 個人にしても組織にしても、社会に対して何を提供し恩恵を出すのかが大事。
  • 最終的には人が価値を判断する。

オブジェクトを支える

  • オブジェクトが期待されるふるまいを続ける。
  • ゴールに沿った論理設計を行い、コードはそれらを素直に表現できること。

所感

  • 『セクションが分かれすぎてそれぞれの部署の話しかしてない』はめちゃくちゃあるある・・小さいチームでさえ起こりうる。
  • みんなが視座を上げるために、どのようなことをしたらいいんだろう。前職(現職)では自分だけ空回ることが多かった。
  • 直感を大事にする、という言葉は今回初めて出てきた。方式も大事だが、経験もすごく大事だよなあ。
  • 何にせよ、フットワーク大事。

全体所感・気づき・メモなど

  • まだゴリゴリにオブジェクト指向を理解できているわけではないから、すこし緊張しながら臨んだが、DDDの話やモデリングの話などコーディングにとどまらない話が多くとても面白かった。
  • 相手と考えをすり合わせるツールとして、オブジェクト指向がいかに便利で有用か分かった。もっと理解するためにも、まずはしっかりコーディングを勉強したいと思った。
  • 御茶ノ水女子大の建物素敵だった。
  • きなり屋のラーメン、うまかった。
  • 自分で考えつつまとめるのうまくなりたい。議事録てきな、スライド読んだ方が早いみたいなまとめ方しかできないな。。。

レポっした。

ClassicASPでセッションタイムアウトを20分以上に設定する(IIS)

はじめに

現在携わっているシステムはいわゆるレガシーシステムというやつで、
ClassicASP&VB.Script(ブラウザはもちろんIE)という、どの懇親会に行っても失笑される(または何それみたいな顔をされる)システムたちを保守しているのだが、
最近「セッションタイムアウト時間を20分から1時間に変更してほしい」という依頼が来たのでいろいろ設定した際、なかなかめんどくさかったのでメモ。

やったこと

IISでの設定

ASP.NETの資料は大量に出てくるのに、ClassicASPの資料全然出てこおへん・・と思いつつ
下記サイト様を参考に、まずWEBサイト>該当のアプリケーションを開き、セッション状態>Cookieの設定内のタイムアウトを設定してみた。

参考
https://blog.goo.ne.jp/greenracoon/e/b8f62f2c640e3cdd143aa67cb60be5b5
https://codeday.me/jp/qa/20190304/358777.html

セッション状態をクリック。

タイムアウトはデフォルトで20分なので、60分に設定してみて、まずは30分後に様子見。


しかしタイムアウトしてしまった。しかも20分で。

むむむ‥と調べたところ、どうやらWEBサイトだけでなく、そのサイトが乗っているアプリケーションプール(DefaultAppPool)の設定をしないといけないらしい。
※ WEBサイトで「インプロセス」という設定をしていると、アプリケーションプールの設定を有効にするらしい。




なので、試しにアプリケーションプールの設定を5分にしてみて、5分後に確認してみたところ5分でセッションが切れた!

これを5分⇒60分にして再度様子を見たところ・・・・またしてもタイムアウトしてしまった!

どうやら20分で切れてしまっている模様。

他の各設定を必死にさがしたところ、
ASP』の中の「セッションプロパティ」「制限プロパティ」のなかにもタイムアウト設定箇所を発見。


そこにあったのか・・・!

こちらも20分になっていたので、とりあえずどちらも1時間に延ばして様子をみたところいけた!

アイドルタイムアウトの優先度的には、アプリケーションプール>WEBサイトの設定なんだろうけど、
そもそもWEBサイトのセッションが先に切れてたらそりゃどうしようもないわな、というような話でした。

そして、よく調べりゃ出てきてた・・・。

参考
http://classicasp.blog.fc2.com/blog-entry-10.html
https://culage.hatenablog.com/entry/20100618/1276786

まとめ

普段IISを意識したり触ったりしてこなかったので、もっと勉強する必要があると反省した。。
また、先人の尊さを敬いつつ、きっとこの世界の片隅で私のようにレガシーなシステムたちに苦しんでいる人たちがいると思うので、
そういう人たちの救いになれるようになるべくこういうことも発信していきたい、そう思ったのでありました。唐突なマザーテレサ感。

おわり。

既定のプログラムを変更する

SQLファイルを開こうとすると、いつもSSMSが立ち上がってしまい、接続状態がないときは時間がかかるわぜんぜん表示されないわでめんどくさかったけど放置していたところ、
ある日仕事でPostgresSQLをインストールしたら、SQLファイルをたたくとPostgresが立ち上がるようになってしまったので、これを機に変更。

 

下記パスにて設定を行う。

コントロール パネル\プログラム\既定のプログラム\関連付けを設定する

 

確認すると、やはりPosgresに変更になっていた。。(デフォルトでそうなるのかな)

f:id:kurunaki1117:20180327101831p:plain

 

いつも右クリックでEditorで開いていたので、既定のプログラムをEmEditorに変更。

f:id:kurunaki1117:20180327101757p:plain

OKを押すと、下記に変更になった。

f:id:kurunaki1117:20180327101916p:plain

別に大したことはしていないけど、いつもめんどくさいなあと思うことを放置しがちなので、小さいところから無駄を省いていきたい。と思う。