144
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ElmAdvent Calendar 2019

Day 1

ヤギと振り返る恥ずかしいコード -- 次のレベルに上がるために

Last updated at Posted at 2019-12-01

これまで未婚の父として娘のヤギさくらちゃんを育ててきましたが、2日後に命を奪う決断をしました。
(12/3(火) 14:22 さくらちゃんは旅立ちました)

ある日、突然目が見えなくなり、次の日には立つことができなくなっていました。
自由にならない体にもどかしさを感じ、目が見えない恐怖にのたうち回りながら
体をぶつけ、顔をぶつけ、歯も折れてぼろぼろになってしまいました。

信頼できる獣医さんの愛にあふれた治療の結果、危篤状態は免れたものの
根本的な回復にはいたらず、2週間の介護を経て安楽死の判断をしました。
1月生まれで間もなく3歳でしたが、その日を迎えることはありません。
人間で言えば18歳くらいでしょうか。

人間の都合で親や兄弟と引き離し、最後は自分の意志とは関係ない第三者によって命を奪われる。
死後は一面のきれいなお花畑で、仲間たちとお花を片っ端からむしゃむしゃ根こそぎ貪り食って過ごしてもらいたいものです。

さて、さくらちゃんを我が家に迎え入れたときのことを思い起こすと、
ちょうどそれは私がElmを使い始めた頃と重なります。
そこで、この記事では在りし日のさくらちゃんを偲びながら、
Elmに不慣れだった頃の私が書いた恥ずかしいコードを供養していきます。

何のために恥ずかしい過去をさらけだすのか

はじめたころの至らぬコードを見るのは、恥ずかしくてたまらないものです。
ましてやそれを公衆の面前にさらけだすことは言わずもがな。

しかし、それを行うことには大きな意味と価値があると信じています。
さくらちゃんが生きた3年に満たない日々にも、人々を癒やす大きな使命があったように。

その価値とは、たとえばElmに入門した人が、次のレベルに行くために。
Elmをはじめたころに自分が書いたコードに対して、今の私自身がどのように恥ずかしさを感じるか。
これはElmに入門された方が、ご自身のコードを次のレベルに持ち上げるための示唆となることでしょう。

そしてElmに触れたことがない人が、雰囲気を感じるために。
過去には当たり前に行われていたことが、今では理由あって非推奨になっていることがあります。
Elmは互換性のために負の遺産を残すことよりも、今をよりよくすることに価値を置き
互換性を失った部分は移行ツールなどで補助する方針を採ることが多いです。
昔の私のコードからそのようなElmの変遷を嗅ぎ取ることもできるでしょう。
おもしろさを感じていただけたのであれば、Elmはどんな人にオススメできないかElmをはじめる人が最初に読むページ をぜひご覧ください。

あるいは、Elmに限らずプログラミング能力を高めたい方のために。
Elmの公式ガイド(和訳版)では、Elmを学ぶことがJavaScriptのコードをよりよいものにすると述べられています。
これはElmの 考え方 が普遍的に他の言語を使う上での新しい「気づき」を与えるものであり、
その「気づき」がいろいろな場所で役立つことを意味しています。
同じように、私の恥の感覚が多くの方に「気づき」を与えることでしょう。

そして、プログラミングに限らず、次のレベルを目指す方のために。
世の中の優れたものは、多くの場合結果だけが強調されます。
結果だけ見ると「なんてすごいレベルなんだ。自分にはそんなもの作れない。」と壁を感じてしまいがちです。
しかし、実際にはその完成形に至るまでに数えられぬ程の試行と修正が積まれています
美しく澄んだ上品なスープは最初から澄んでいたのではなく、幾度となくアクをとり、澄んだかと思えばまた出てくるアクをとり、
そうしてはじめて雑味が消えて完成するものです。
文章だって数十回以上の推敲作業の末に、読みやすくおもしろい、ときに感動的ですぅっと体に染み込むものになるものです。

最初は必ず濁って淀んで雑味に満ちたもの。
でもそれを作らずして知識ばかり増やしたところで何も始まらない。
私自身も学生時代には、文法を覚えたプログラミング言語の数を数えて喜んだり、難しい言語機能を理解することにばかり気持ちよさを感じていました。
ポシェとかエスプーマとかプリウスとかカルカソンヌとか、たくさんのかっこいい調理方法をそれぞれ単体で覚えたところで、
拙いながらもそれらを複合的に使って料理を作ることをしなければ、いつまでも本当のいい料理は作れません。

YAGI にも YAGNI

前置きが長くなりましたが、さっそく過去の恥ずかしいコードを見ていきましょう。
最初のテーマは YAGNI(ヤグニ) です。
YAGI(ヤギ) ではなく YAGNI(ヤグニ) です。
チキンビリヤニでもなく、"You ain't gonna need it." を略したものらしいです。
要するに「まだいらんやろ」ってことです。

ヤギの飼育、特に室内での飼育とは未知なるものです。
マイナーすぎて誰も情報発信しません。
かわいい姿や面白い姿は需要があるのでシェアしますが、
床に敷くマットのオススメや家を留守にするときの注意点などはほとんど共有されません。
さらに、ヤギには個体ごとに特性がありますから、干し草を食べさせるにしても
どこの国でいつ採れたものか、何番刈りなのか、シングルプレスかダブルプレスがいいのかなどは実際に試してみないとわかりません。
かといって、「干し草を食べなかった場合に備えなければ」と先にあらゆるタイプのご飯を買ってきて用意したら、
結局食べきれなくて腐らせてしまいます。

こういうときに役に立つのがYAGNIの考え方です。

  • とりあえず何かご飯を食べさせないといけないので、近所でモルモット用に売ってるチモシーを買ってきてみる
  • 食べさせてみたら、硬い茎の部分ばかり残している
  • だったらもう少し柔らかいものが必要だ
  • そこで試しに三番刈りダブルプレスのチモシーを少しamazonで買ってみる
  • 気に入っているようだったら、少量ずつ買っていては高く付くので20kgずつ購入してみる
  • ちょっと太ってきたらクレイングラスを試してみる

このように、いまどうしても必要なものだけ逐次用意して、実際に他のものが必要になった段階で初めて用意するという戦略です。

Elmにおいても、かつて私はこのYAGNIができていませんでした。

module Model exposing
    ( Goat
    , Model
    , age
    , goat
    , name
    , ...
    , ...
    )

import Monocle.Lens exposing (Lens)


type alias Model =
    { goat : Goat
    , ...
    , ...
    }


type alias Goat =
    { name : String
    , age : Int
    , ...
    , ...
    }


goat : Lens { x | goat : a } a
goat =
    let
        get =
            .goat

        set v a =
            { a
                | goat = v
            }
    in
    Lens get set


name : Lens { x | name : a } a
name =
    let
        get =
            .name

        set v a =
            { a
                | name = v
            }
    in
    Lens get set
...
...

最初にimportしている Lens というモジュールは、「なんかかっこよく値の更新とかができるよ」というなんかなんとなくかっこいいやつです。
この Lens をつかって定義された goatname といった関数は、以下のように「かっこよく」使います。

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Mask ->
            ( model
                |> .set
                    (Lens.compose Model.goat Model.name)
                    "(個人情報保護のため表示できません)"
                |> Lens.modify
                    (Lens.compose Model.goat Model.age)
                    (\n -> max 0 (n - 2))
            , Cmd.none
            )
        ... ->
        ... ->

なんかよく分からんけどかっこいいですね! なんかよく分からんけど。

でもよく考えたら、こんなに中二臭い書き方をしなくても、もっと素直に読みやすく書けますね!

update : Msg -> Model -> ( Model, Cmd Msg )
update msg ({ goat } as model) =
    case msg of
        Mask ->
            ( { model
                | goat =
                    { goat
                        | name = "(個人情報保護のため表示できません)"
                        , age = max 0 (goat.age - 2)
                    }
              }
            , Cmd.none
            )

こういう書き方でもスッキリします。

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Mask ->
            ( { model
                | goat = maskGoat model.goat
              }
            , Cmd.none
            )


maskGoat : Goat -> Goat
maskGoat goat =
    { goat
        | name = "(個人情報保護のため表示できません)"
        , age = max 0 (goat.age - 2)
    }

もちろん、Lens が役に立つようなことも、いつかあるかもしれません。
そりゃ、奇跡が起きてさくらちゃんが明後日までに急にすべて元通り元気になる可能性もゼロじゃないですからね!

でも、少なくとも Lens がどうしても必要になるまでは導入する必要はないですし、
よりによって最初の例のように全フィールドに対して Lens を最初から定義する必要はないはずです。

なんとも恥ずかしいコードでした。

自分の都合を押し付けない

ヤギとは本来集団行動が苦手な動物です。
一方でひとりにされると寂しくて「ぶべぇぇぇぇ...」って鳴いちゃう動物です。
僕も集団行動はしたくないけど、家にひとりでいると寂しい動物なので、もうすぐさくらちゃんがいなくなって「ぶべぇぇぇぇ...」って泣いちゃいます。

そんなヤギさんなので、自分がしたいように自由にさせてあげないと、ストレスがたまってしまいます。
僕も目覚ましなんてかけずに自分が起きたい時に起きて、さくらちゃんと遊びたい時に遊ばないと、ストレスがたまってしまいます。これからどうしよう。

では、たとえばヤギさんが床に敷いている防水マットを食べだしてしまったらどうしたらいいでしょうか?
あきらかに体に悪いので食べさせたくないですし、このままではマットがなくなってしまいます。
だからといって叱っても、「なんでお父さんはいじわるするのっ!!」と頭突きされるだけで何も変わりません。

この場合は、なぜマットをかじるのかを考える必要があります。
単純に満たされぬ食欲に支配されているのかもしれません。
うまく立てなくなった今でも、咀嚼がうまくできない今でも、ご飯をあげると「もっとくれ、もっとくれ」と暴れだします。
ヤギの生命力の源はこの食欲なのだと感じる瞬間です。

食欲が原因なら、ダイエット効果があるクレイングラスを食べ放題にする解決策もあるでしょう。
あるいは歯がなんだかムズムズするのが原因なら、定期的にアルファルファキューブをあげてかじらせておけば解決します。

さて、Elmの場合はどうでしょうか?
Elmは自分の意志と哲学、主張をしっかり持っている生き物です。
Elmがやりたがらないことを強制しても、拒絶反応を示してさまざまな弊害を生みます。

たとえば、Elmは生のHTMLを表示することをひどく嫌がります。
そんなElmに無理にHTMLを表示させた結果、バージョン0.19になったときに拒否されてしまい、「Elmで生のHTMLをそのまま表示させる」のような正規の方法で実装し直す必要が生じました。

さらに、自分の願望を押し付けることばかりに夢中で目的を忘れてしまうと、余計な遠回りをすることになります。
たとえばCSSの名前衝突を避ける目的のためだけに、elm-css-modules-loaderなんていう悪いハックを使った結果、生産性を下げ、また0.19になったときにしばらく使えなくなりました。
Elmのありのままの姿を受け入れたら、本当はBEM記法とか気軽な方法で十分でした。

知識の探求をやめない

YAGNIは「必要になるまでやない」ことでした。
「知らないからやない」ではありません。

覚えたことをついつい使いたくなる気持ちをおさえながら、
同時に知識を探求し、選択肢を増やしていく必要があります。

たとえば、ヤギの病気については病気になってから調べたのでは手遅れです。
病気についての情報を事前に得ておいて、必要に応じて予防策を打っていく必要があります。

ところで、ヤギにありがちな病気のひとつに腰麻痺があります。
さくらちゃんを迎え入れた頃に、その予防のために北浦和にある、ヤギも診れると書いてある動物病院に問い合わせたところ
「予防薬を入荷したら連絡する」
と言われ、1ヶ月後に確認のため再度電話しても
「入荷に時間がかかっている」
と回答があり、それから今日にいたるまで連絡はありませんでした。
きっとなにかのっぴきならないご事情があったのでしょう。

一方で、浦和の直井動物病院は大変すばらしい病院です。
ふつうの動物病院は犬か猫しか診れないなか、ヤギも診られるくらいに技術もすばらしく、ペットにも飼い主に対してもホスピタリティにあふれ、
情熱を持って治療にあたり、特殊な症例のさくらちゃんのためにアメリカの文献にあたったり、
各種機関に問い合わせながら治療に当たってくださいました。

もちろん、病気についての知識を得ていても、今回のように原因不明の脳炎のような症状で苦しませてしまうこともあるでしょう。
それでも、ご飯の与え方やヒヅメのケア、鼓腸症などへの配慮について、直井動物病院の主治医から認められたことは、私の中でさくらちゃんへの罪悪感を薄めて前を向いて生きる力になりました。
このように、常に知識を求める姿勢は大きな価値をもたらしてくれます。

それではここで知識がなかった頃の恥ずかしいコードをお見せします。

module Util.Maybe exposing (..)


maybe : a -> (b -> a) -> Maybe b -> a
maybe def f =
    Maybe.withDefault def << Maybe.map f

maybe-extra の unwrap と同じものをわざわざ定義して使っています。
Elmのコンパイラーは dead code elimination を行うので、1つの関数だけのために気軽にパッケージをimportできます。
特にmaybe-extraelm-community のパッケージなので、使わない理由もありません。
他にも、list-extra など、 elm-community/*-extra パッケージのことを知らなかった頃の恥ずかしい思い出です。

別の例もお見せします。

import Html exposing (Html, div)
import Html.Attributes exposing (id, class)
import Html.Events as Events

view : Model -> Html Msg
view model =
    div
        (List.filterMap identity
            [ Just <| class "parent"
            , Just <| id "view-main"
            , if model.show then
                Nothing

              else
                Just <| Events.onClick ClickViewMain
            ]
        )
        []

条件によってあったりなかったりする属性をあつかいたい場合です。
簡単なテクニックですが、こういう場合には class "" を使うと便利です。

import Html exposing (Html, div)
import Html.Attributes exposing (id, class)
import Html.Events as Events

view : Model -> Html Msg
view model =
    div
        [ class "parent"
        , id "view-main"
        , if model.show then
            class ""

          else
            Events.onClick ClickViewMain
        ]
        []

もし属性ではなく要素(Html msg型の値)の方が、条件によってあったりなかったりする場合は、代わりに text "" を使うと便利です。

最後に、もうひとつ恥ずかしい例をお見せしてこの項目を締めくくります。

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Mask ->
            ( { model
                | goat = maskGoat model.goat
              }
            , Task.perform (\() -> PostProcess) <| Task.succeed ()
            )

        PostProcess ->
            ...
            ...

ある Message を処理した後に、別の Message を呼び出したいときのアンチパターンです。
次のように書きかえましょう。

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Mask ->
            update PostProcess
                { model
                    | goat = maskGoat model.goat
                }

        PostProcess ->
            ...
            ...

更新後の Model を自分自身(update 関数)にそのまま渡すだけです。

進化を続ける

昔を思い出して恥ずかしくなるのは、自分自身が当時より進化しているからです。

「なぜもっと早くこうしなかったのか」と思うことがよくあります。
たとえば、さくらちゃんのおトイレも何度か進化してきました。
当初はカバー付きの市販のものでしたが、今では二重底で猫砂を敷いてあり、
おトイレに座ってもお腹が汚れず、また頻繁にペットシーツを交換する手間がなくなりました。

Elmにおいても、いま思い返すと

など、「なぜこんなあって当たり前のものを今まで作らなかったのか」と不思議にすら思います。

さくらちゃんもきっと、いなくなるのではなく、新しい概念上の存在に進化するのでしょう。
今までの物理的法則に縛られた体から、みんなの思い出の中を縦横無尽に行き来できる体にレベルアップして生き続けてくれます。

僕も進化を続けるから、見守っててね、さくらちゃん。

さくらちゃんの追悼写真集を手に入れる
さくらちゃんをもっと見る
他の記事を見る

144
55
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
144
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?