ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

Pathname#join の振る舞いが気になってbugs.ruby-lang.orgにissueを立てた話

ESM Advent Calendar 2022 - Adventar 8日目の記事です。

こんにちは color_box です。

Rubyのバグ報告や機能の提案などを議論するbugs.ruby-lang.orgに初めてissueを立てる機会がありました。立てたのは下記のissueなのですが、これを作成するまでにいくつか知見が得られたため、それについて書きます。

bugs.ruby-lang.org

まずPathname#joinの利用時に下記のような挙動に遭遇しました。

irb(main):003:0> Pathname('/foo').join('bar', 'baz')
=> #<Pathname:/foo/bar/baz>
irb(main):004:0> Pathname('/foo').join('bar', '/baz')
=> #<Pathname:/baz>
irb(main):005:0> 

Pathname#joinには複数のディレクトリ名を引数として渡すとそれらを連結したものが得られます。 しかし、複数ある引数の中に絶対パスで記述されたものが入っていると、それ以前の引数が全て無視され、絶対パスの引数以降の引数で返り値が組み立て直されます。 こちらの挙動に気づいた時にRubyリファレンスマニュアルなどを確認しましたが、この挙動に関する記述を見つけることはできませんでした。

これについて社内外の有識者に相談したところ、いくつかの知見が得られました。

Is this intentional?

まずこちらの資料です。 Rubyのissue作成時の考え方について書かれた資料です。 どの程度の記載でissueを作成すべきかに基準が明確になるため、issueの作成時に参考になりました。

意図されているかはテストコードを見ることでわかる

特定の挙動が意図されているかどうかは、ドキュメントを見てもわからないことがあります。 その場合、テストコードを読むことでその挙動が意図されているかどうかが判別できます。 テストとしてその挙動を確認している箇所があればその挙動はテスト対象であり、意図されたものであるということがわかるからです。

https://github.com/ruby/ruby/tree/master/test

(ただ、意図されたものであることがわかってもその理由まではわからないままであることもあります)

今回作成したissueについては下記のようなテストが書かれていたことが後からわかり、少なくとも意図したものであることがわかりました。

github.com

ハードルを上げすぎない

issueを上げる時、他言語の似たような仕様について調べて、それらも付記すべきと考えていました。 しかし、それについて相談したところ逆に今回のようなケースではそれはやめたほうが良いという助言をいただきました。 理由は、その調査によってハードルが上がること、今回出そうとしている内容では必須ではない、の2点です。

出そうとしていたissueは特定の挙動が意図したものか、そして可能であればその理由を確認するためのものです。 「他言語でこうなっているのでこうすべき」というような修正提案ではないため、他言語の仕様を調べる必要はないという話でした。

まとめ

上記で紹介したような知見をいただき、背中を押してもらうことでissueを作成することできました。

bugs.ruby-lang.org

記事執筆時点でドキュメントへの追記が行われ、issueはcloseされています。

おまけ:他言語の類似ライブラリの挙動

OSSパッチ会でこの件について相談した時、他の言語の類似ライブラリの挙動について共有を頂いたため、それらについてまとめておきます。

Python

docs.python.org

実際の挙動:

import os

os.path.join('/foo', 'bar', '/baz/asdf', 'quux', '..') # '/baz/asdf/quux/..'

Go

pkg.go.dev

実際の挙動:

package main

import (
    "fmt"
    "path"
)

func main() {
    fmt.Println(path.Join("a", "b", "c"))           // a/b/c
    fmt.Println(path.Join("a", "/b"))               // a/b
    fmt.Println(path.Join("a", "/b", "/c", "../d")) // a/b/d
}

[Go] go 1.16.3 - Wandbox

JavaScript

www.w3schools.com

実際の挙動:

const path = require("path");

console.log(path.join('/foo', 'bar', '/baz/asdf', 'quux', '..')); // `/foo/bar/baz/asdf` 

[JavaScript] Node.js 16.14.0 - Wandbox


パッチ会にて、他言語の挙動について調べてくれた @yu_suke1994 さん、ありがとうございました

謝辞

最後になりますがパッチ会で相談に応じてくださった @koicさん @yahondaさん @yu_suke1994さん @amatsuda さん ありがとうございました。

パッチ会は毎月開催しております。

blog.agile.esm.co.jp