ぎんさんマインド

いちエンジニアの思考とか趣味についてつらつらと書いてみるかもしれない。

ChatGPTで対話しつつRaspberry Piでマインクラフトサーバーを立ち上げた

最近所謂シムゲーと言われる街づくりゲームにはまってるので、その流れでマインクラフトやり直したくなった。
なのでちょっと立ち上げようかなと思いやってみた。



とりあえずやり方と調べてみた感じ、散々擦られたネタなので立ち上げ方はたくさんあるけど、逆にありすぎてクリティカルな情報が得られない感じがした。
なのでざっくりとした理解はChatGPTに聞いて、抽象的なものや嘘情報はその都度深度増して調べていけばいいかなという感じでやった。


バージョンはちょっと古いものを提示してくるものの、概ねいい感じの情報は与えてくれるのでとても助かる。

他にもこのようにちょっとした要望に返答してくれたり。

エラーに対してどういうものか提示してくれたり、なかなかに良い対話相手になってくれた。



まぁ結局正当性を判断するために片っ端から調べながら進めたしちょっと間違ったことも言われたけど、立ち上げられたので満足です。

github projectsでゲームを管理する

steamセールで買ったゲームが多くて何を買ったのか見えなくなってきた。
だったら仕事でもよく使うしgithub projectsで管理しちゃおうと思ったので書いてみる。


例としてはこんな感じ
github.com



作り方は公式のdocを参照
docs.github.com

ぱっと見でわかりやすいのでboard形式が良い。

何か補足があればdescription書けばいいし、問題があるならissueにすれば良いので汎用性も高い。

何よりこれを見ればSteamセールで無駄なゲームを買わなくてよくなるので節約になる。

コードをコードで説明するな

学生時代に先生に言われた記憶がある。
その当時はレポートの再提出も重なってたので負の感情も含めて受け止めてたけど、今思うととても重要な言葉だったなぁと感じる。
これによって私のプログラミングのスキルもある程度担保されてるところがあるし、プログラマとして初心者かどうか判別する判断材料としても利用してる。
今になってはそれくらい大事にしてる考え方なので書く。


例えばこのようなプログラムを説明する際に

 user = User.new(email: 'test@example.com')

「emailを'test@example.com'にしてUserをnewしたuserを作っています。」

相手が初心者であればこんなことを言われる場合もある。
ただ、これは説明してるとは言えない。プログラムを音読しているだけという方が正しい。

答えるのなら
「ユーザークラスのインスタンスを作成、メールアドレスには'test@example.com'を代入しuserという変数に代入しています。」
のように返すべきだと思う


これにより何が変わるのか。

まず第一に理解しやすい。
日本語とプログラミング言語を混ぜたルー語のようなものを言われるよりも、全て日本で説明された方が明らかに聞きやすい。



第二に相手の習熟度がわかる。
例えばRubyにおけるUser#newが何をしているのか理解していない人ならば後者の説明が行えない。
簡単に言えば行ってることはインスタンスの作成であることから、理解しやすい処理だと思われるのでオブジェクト指向としての理解度がわかる。
もしもこれ以上の技術力を持っていることがわかっているのなら、もっと深い階層で理解し得ていない部分があるのかとも窺える。


これらの要因からプログラムはちゃんと適した言語で説明すべきだろう。

Deviseを読み解く

Rails appで認証フローにおいてよく使われるDeviseだが、かなりやっていることも多く中身もブラックボックスとなっていて見にくい部分も多い。
だが調べると簡単な導入方法や使い方ばかり出てくるので残しておく。

Deviseとは

そもそもDeviseとはwardenをベースに認証のソリューションを提供するgemである。
基本的にはCokkieにトークンを保管し認可する方式をとっていて、
RailsのようなMVCベースで構築し、10のモジュールを提供しているので、必要なものだけ使ってくださいと言う形。
一覧としては以下

Database Authenticatable データベースによる認証サインインの提供、パスワードをハッシュして保存したり、パスワード変更時のメール通知などを提供
Omniauthable OAuth認証をサポート
Confirmable 認証確認手順を提供、アカウント登録時にメールを送信するなど
Recoverable アカウントの復旧、パスワードのリセット処理など
Registerable ユーザーのサインアップやアカウントの編集削除処理などを提供
Rememberable クッキーに保管されたユーザーを判別するためのトークンの生成削除
Trackable サインインの回数、日時、IPアドレスなどを管理
Timeoutable セッション有効期限の設定管理
Validatable メールやパスワードに対するオプションの提供
Lockable アカウントロック処理の提供


基本はRailsをベースにしているのでjwt認証や2段階認証はないが、別で作られている
https://github.com/waiting-for-dev/devise-jwt https://github.com/tinfoil/devise-two-factor
そのほかrubygemsで調べると色々出てくる
https://rubygems.org/search?query=devise

作成時の挙動から

rails g devise usersした時にcontrollerに
https://github.com/heartcombo/devise/tree/main/lib/generators/templates/controllers
をusers配下に一通り作成する。
作成されたcontrollerは
github.com
のクラスを継承しているので、挙動の確認に困った場合はここから見ていくと良い。
各々がやってることはmoduleを元に見ていけばわかる。

ユーザーテーブルに干渉するカラム

https://github.com/heartcombo/devise/blob/main/lib/generators/mongoid/devise_generator.rb#L24-L53
を参照してみるとデフォルト作る場合はこれだけカラムを設定している。
設定より規約を重視するgemであれば、どこを見ればわからないときはgeneratorsでデフォルトで入るカラム一覧をみることでわかりそう。

ABC予想の問題を読む

日課のニュースをふらふらみていたら、今更ながらABC予想の文字が見えた。
ずっと昔にどういう問題なのかなー?気になったけど、結局詳しく調べもせずに終わったので、改めて読み解いてみる。

問題

ja.wikipedia.org

a + b = c
を満たす、互いに素な自然数の組 (a, b, c) に対し、積 abc の互いに異なる素因数の積を d と表す。
このとき、任意の ε > 0 に対して、
c > d^1+ε
を満たす組 (a, b, c) は高々有限個しか存在しないであろうか?

解説

上記がABC予想と言われている。

最初の文、a + b = c を満たすお互いに素な自然数の組はわかりやすい。
a = 1, b = 2として例えるなら 1 + 2 = 3 つまりc = 3であるということになる。

ここから 積 abc の互いに異なる素因数の積 1 * 2 * 3 = 6 つまりd =6と表した時に
c > d ^ 1 + ε (ただしε >0)となるような値を考えていこうということになる。

ざっくりとした説明であればこんな感じなので、もうちょっと詳しくみていく。

まず、「互いに素な自然数の組である」という点、これは最大公約数が 1 であるともいえる。
つまり互いの数値が1以外に同じ同じ数字で割れないということになる。
a = 2, b = 4の場合だとaもbも2で割れるので当てはまらないということになる。

次に、「積 abc の互いに異なる素因数の積」
これは、abcをそのまま掛けるのではなく、それぞれを素因数分解し、その一意な値同士を掛けるということである。
例を出すならば a = 1, b = 8だとわかりやすい
a + b = c から 1 + 8 = 9, c = 9 になるが
b = 2^3 ,c = 3^2 であるため
1 * 8 * 9 は 1 * 2 * 2 * 2 * 3 * 3 とも書ける。ここから同じ値をはぶき
1 * 2 * 3 = 6 つまりd = 6であるということである。

さらにこれは c > d であるためこれ以降の説明にも使えそうである。

問題文には
c > d^ 1 + ε と書かれているが、一旦εの値を無視してみると c > d と同じであり上記のa = 1, b = 8でも成り立ちそうである。
ただ、ε > 0であることからdは1より上の冪乗をする必要があるということになる。
仮にεを0.1にした場合は 6 ^ 1.1 = 7.17738719311 つまり、9 > 7.17738719311となる。
これでも成り立つため、これはABC予想の問題に当てはまるということになるが、問われているのはこれが無限にあるのか、有限個なのかである。
a を無限に増やした時、bを無限に増やした時にどうなるかを探り証明するのがこの問題の主旨になる。


問題をさらにわかりやすく理解するために、この計算をプログラムで表し、列挙してみた
github.com

全て列挙するというのは流石に無理だが、ある程度列挙はできるようだ。
中に書かれているepsilonは、問題に当てはまらない最小の値を表示したので簡単な指標としてみてほしい。
a <=10000, b <=10000 まで計算してみたけど、
a,bの値が上がるにつれてεの値が下がっていくのであればわかりやすかったけど、そんなこともないようで。

流石は証明が正しいかどうかに5年もかけた問題は違うなぁ。

Railsにおけるエラーハンドリング

序文

について教えようとしていた際にRailsチュートリアルなどで記載がなかった。
一応例として
Error Handling in Rails — The Modular Way | by Sudharsanan Muralidharan | Rails, Ember & Beyond | Medium
があったが、古いのと英文であるため、似ている部分が多いが残しておきます。

本文

例えばユーザーを取得する際にControllerではこのように記載するのがメジャーだと思います。

class UserController < ApplicationController
    def show 
        @user = User.find(params[:id])
        render json: @user, status: :ok
    end
end

この時どのようなエラーが返却されるか考えてみます。

一番わかりやすいのはIDがUserになかった場合、ApplicationRecord#findにてActiveRecord::RecordNotFoundエラーが発生などでしょうか。

ただ上記の記述の場合、このエラーが発生した時にエラーハンドリングが記載されていないため、親クラスへ飛びます

簡単に言うと404の画面がレンダリングされるって感じです。

ただそれで本当にいいのでしょうか?


emailで取得する場合を考えるとわかりやすいかもしれません。

画面上でユーザーがemailを入力して、ちょっと間違えてしまった時に画面が切り替わってエラー画面が表示されるなんていうのはよくない形ですね。
一般的には、「入力したemailが間違ってます。」のようなメッセージを表示して戻されます。

この処理をControllerに加える場合どうするべきでしょうか?

class UserController < ApplicationController
    def show 
        @user = User.find_by!(email: params[:email])
        render json: @user, status: :ok
    rescue => e
        render json: {message: '入力したemailが間違ってます。'}, status: : not_found
    end
end


上記の場合だと入力されたemailに対し、Userが存在しない場合はエラーメッセージを返却する形になっています。
ただ、それ以外のエラーに対しても同じ内容を返しているのでよくない形ですね。

例えば、DataBase Serverが落ちた場合、Typoした場合、この後処理を追加したがそれが間違っていた場合などが挙げられます。
これはあり得ないことではないです。
※落としたトーストがバターを塗った面を下にして着地する確率は、絨毯の値段に比例する(マーフィーの法則)
※失敗する余地があるなら、失敗する(マーフィーの法則)
※ありえないなんてことはありえない(鋼の錬金術師 より グリード)

その場合でも「入力したemailが間違ってます。」とメッセージを表示するのはおかしいのではないでしょうか?


上記の場合であれば500エラーの画面を表示するなどが適切です。
そのため、Controllerは

class UserController < ApplicationController
    def show 
        @user = User.find_by!(email: params[:email])
        render json: @user, status: :ok
    rescue ActiveRecord::RecordNotFound => e
        render json: {message: e.to_s}, status: : not_found
    end
end


class ApplicationController < ActionController::Base
    rescue_from StandardError, with: :render_500

   def render_500(e)
       Rails.logger.error(e.message)
       render :error, status: :internal_server_error
   end
end

DRY原則や保守性の観点からエラーハンドリングは親クラスにて行うのが良いです。
さらに言うと、ApplicationControllerには各ロジックが加わるため、モジュールで分離する方が良いです。
そのため

class ApplicationController < ActionController::Base
    include ErrorHandler

end


module ErrorHandler
    extend ActiveSupport::Concern
    included do
        rescue_from StandardError, with: :render_500
    end

   def render_500(e)
       Rails.logger.error(e.message)
       render :error, status: :internal_server_error
   end
end


このerror_handler.rbは例として'lib/errors'などに置いておくのも良いですが、各ドメインで処理を分けている場合などならその都度改装を分けておくのも良いです。
参考資料ではself.includedを用いてますが、railsであればActiveSupport::Concernに加えるのも良いかと思います。
別途Sentryなどのエラー監視を行う場合はrender_xxxメソッドに追加して監視できます。

また特有のエラークラスを定義したい場合は config/exception.rb を作成し

 class UserNotFoundError < StandardError; end

のように独自定義し

class UserController < ApplicationController
    def show 
        @user = User.find_by(email: params[:email])
        raise UserNotFoundError.new('ユーザーが見つかりませんでした。') if @user.blank?
        render json: @user, status: :ok
    end
end

のように使うのも良いです。
参考としているものだと、親クラスを作成していますが、基本的にRuby,Railsで定義されているErrorクラスだけでも良い場合が多いためそこまでしなくてもいいかなと私は思います。

既存サービスを一歩づつモノレポにしていく

概要

仕事で色々なサービスに携わっていると、 さまざまな歴史的経緯で生まれた多くのアプリケーションを目にする。


会社を立ち上げた当初に作られたサービスが数年経ち、メンテナンスもされずにツギハギで修正された結果手のつけにくいものになったとか、事業的には乗っているので、更なるサービスを開発しようとし、認証基盤だけは共同で使おうと無理矢理過去のサービスを呼び出したり、もしくは、だったら作り直そうと認証基盤のみ外に出してそれだけでも動くようにしたり色々ある。


そうやっていろんな連携を含んだサービスにおいて開発環境もあまり整ってなく、だったらとdockerの上に乗せてまとめて開発環境を作るなんてことがあったので残しておく

構成

github.com
簡単に作ってみたがこんな感じ

ざっくりと説明すると、既存のアプリ1とアプリ2があり、それを連携させた上で両方構築できるようその一つ上の階層でdocker-compose.ymlを作って、両方まとめた環境を立ち上げる。

参照するdockerfileはそれぞれのアプリ内においてあるならそれを参照してもいいし、そことは違うところにおいておけるようディレクトリ作ってもいいと思う。今回は別のディレクトリを作ってそっちにおいておいた。

これ自体は単体のレポジトリとしておいておいて、作業管理、zenhub連携してサービス全体のタスク管理や大きなissueをおいておいてもいいと思うが、各々のサービス全部内包させるとgit管理が多すぎて面倒になるので .gitignoreなどで弾いておきたい。
おそらくそれぞれのサービス単体でも細かいissueは出てくると思うので、それはそちらで管理する体制にしたい。

体勢

開発環境なんてのはどこも作られてると思うが、それを変えようとはあまり思わないと思う。
というかそのプロジェクトの中でできる人間がサラッと変えていて、それをpullしていつの間にか使いやすくなってたなんて感じの方がよくあるかなと個人的に思ってる。やった人もっとコミュニケーションをしたいから発言してくれ。

そんな感じでdockerをいじる人間は少数になりがちだし、開発環境がどう作られるのか知らない人も多くなりがちなので、なるべく良い状態で使ってもらって周りからの信頼を得たい。
簡単に言えば、プロジェクトに新規参入したときに「開発環境で2日はかかるんだよねー」なんて言われた時に上記のように直して、「これで半日で終わりますよ、なんならコマンドほとんど打たずダウンロードの時間だけで済みますよ!」なんて言ってやりたい。
この辺は私のトークスキルが足りないので願望。



一応備忘録なのでこんなもんで