sironekotoroの日記

Perl で楽をしたい

年末年始に母のスマホ移行に付き合う

母のスマホ購入

元々、母からスマホを利用したいということを聞いていました。

職場(保育園の看護師)や民生委員の活動、友達との集いで LINE の利用が多いとのこと。

LINE を使うためにスマホに変えたいということでした。

母は iPad を利用してニュースやクックパッドをみており、2013年頃からのユーザーです。

タッチインターフェースには慣れていると判断し、同じ操作感の iPhone を購入となりました。

また、実家近くに住む妹が iPhone ユーザーであり、甥っ子姪っ子も iPad ユーザーであるので、聞ける人が周りにいるというのも決め手でした。

3D LiDER のような機能は不要ということで、iPhone SE(第3世代)への機種変を予定しました。

Docomo ショップ

年末の帰省に合わせ Docomo ショップに機種変の予約を入れます。

しかし、母の Docomo ID がわからないため、自分の Docomo ID を使って機種変の予約をします。

予約フォームに備考欄といった自由入力欄はありません。

母に付き添って Docomo ショップへ向かい、来店早々「すみません、実は私ではなく親の機種変で・・・」という説明からすることになります。

お目当ての iPhone SE(第3世代) はブラックしか在庫がありませんでした。

母はホワイト希望だったのですが、入荷はひと月先とのこと。

ということで、在庫のあるブラックを購入します。

事前に希望の機種がある場合は、早めに予約しておくのが良いようです。

機種変についてはタブレットで説明動画を見せられて、いくつか質問をして終わりました。

Apple ID の認証情報は iPad に設定していたもと同じものを母に代わって入力します。

この辺りはスムーズだったと思いますが、それでも1時間はかかりました。

心配していた連絡先ですが、ガラケーの電話帳から連絡先を移行する機械があり、全く手間なく移行を終えることができました。

ここは Docomo ショップを使った甲斐があったというものでした。

チュートリアル

iPad でタッチインターフェースに慣れているとはいえ、電話やメールの機能は使っていない母。

以下を一緒に行います。

  • 電話の掛け方、受け方

  • メールの送信の仕方、メールの読み方、返信の仕方

  • 写真の撮り方、撮った写真の確認の仕方

これでガラケーで出来たことは iPhone でもできるようになった感じです。

そういえば、iPhone の初期の壁紙が 3Dの視差効果付き、かつ現在時刻の一部が隠れるようなものでした。

これを見た母は初期不良かと思ったそうです。

備品購入

引き続き、以下の備品を購入しました。

ケースとガラスフィルムはつけて当然というか。

ワイヤレス充電は、その方が楽であろうということで。

モバイルバッテリーと Lightning コードはガラケーに比べて電池の持ちが良くないスマホのために。

そして最後の本。

一通りの基本アプリの操作が載っている上に、 LINE についても一章を割いて解説してくれています。

実は自分は LINE を使っていなかったので、とても助かりました。

LINE にサインアップすると・・・

・・・LINEって、サービス初期からこうだったよなぁ・・・(だから使っていなかった)

ja.wikipedia.org

インストールしたアプリと各種認証情報

LINEの他にインストールして設定したのは以下のアプリです。

クックパッドやニュースのアプリは画面が大きい iPad に入れており、 iPhone には不要と判断したので、最低限のもののみです。

  • 接種証明書アプリ

  • マイナポータル

  • マイナポイント

また、Apple ID などの認証情報は紙に書いて渡しています。

パスワード管理アプリというのもあるのですが、まずは使う敷居を低くするところから。

なお、ウォレットや Apple Pay の設定はしていません。

その後

母はその後、 Docomo ショップのスマホ教室にいって色々と学んでいるとのこと。

これも Docomo ショップを使っておいてよかったなぁという点です。

月々の料金を考えると ahamo とかいろいろ考えました。

しかし、何かあった時の対応が自助努力になること(その代わり安い)を考えると、少々高くても実店舗のある Docomo ショップに頼れるのは助かります。

ということで、年末年始の帰省で母のスマホ移行に付き合った話でした。

2022年もお疲れ様でした

2022年を振り返って

お仕事が忙しかったりなんなりで、2ヶ月も空いてしまいました。

Perl入学式

in 東京の講師と、テキストの改修を担当しておりました。

今年は春、秋と開催し、オフラインでの講義に切り替えました。

やはり近くに講義を受けている方がいて、ダイレクトに質問を受けられるのはすばらしい!と言う感想です。

来年もまた、オフラインでの開催をやっていきたいと考えています。

「良いコード/悪いコードで学ぶ設計入門」

一通り読み通すことができました。

何も考えなかったり、雑に書くと詰まってしまうところを、この技法で読みやすくメンテナンスしやすくしよう・・・という本でした。

そしてそれ以上に、他の言語の本を Perl に読み替えて学習しきったという体験がとても良かったです。

気になる本があっても、Perl じゃないというだけで敬遠してたのですが、そういう壁を乗り越えるきっかけになりました。

・・・その勢いてたくさん本を買い、積んでしまっていますが。

積んでいる本のトップはこちらになります。

お仕事

今までやってきたお仕事が減るかも、と思ったら別の仕事が増えたり、といった2023年になりそうです。

とにかく、楽をできるように上手く技術使っていきたいです。

2023年もよろしく

というわけで、来年も頑張っていきます。

「良いコード/悪いコードで学ぶ設計入門」第13章 モデリング 〜 第14章 リファクタリング

13章もパラパラっとページをめくってみた感じ、文章がメインぽいですね。

13.1 邪悪な構造に陥りがちな User クラス

面白い。いかにもありそうな気がする。

  • User というログインユーザーを示すクラスを作る
  • そこにIDや名前などのプロパティを追加していく
  • User を管理するための UserManager というクラスも作る
  • さらに法人ユーザーも同じ User クラスを使い(ん?)
  • 法人番号などのプロパティを追加していく(個人ユーザー使わないよね、そのプロパティ)
  • 法人ユーザー を管理するための CorporationManager というクラスも作る

そして、UserMaganeger, CorporationManager 双方が User クラスを見た時・・・

  • CorporationManager が個人 User の法人番号が空白としてエラー(個人ユーザーだし当然)
  • UserManager が人名に利用できない (株) などの文字を利用しているとしてエラー

これらを回避するために、分岐が増え・・・メンテナンスが難しくなり・・・おぉ、目に見えるようだ。

13.2 モデリングの考え方とあるべき構造

  • モデルはシステム構造の説明のために用いる
  • システムとは何か?
  • システムは目的達成のための手段
  • 特定の目的達成のために、最低限考慮が必要な要素を備えたものがモデル

ここで、例として通販サイトにおける商品モデルを取り上げるが、商品を構成するプロパティを全部突っ込んである巨大な商品モデルになっている。

これを、「注文時の商品モデル」「配送時の商品モデル」のように、目的ごとに定義した商品モデルにする。

13.3 よくないモデルの問題点と解決方法

上記の User モデルは、複数の目的のために無理やり利用されており、モデリングしているようでモデリングしていないといえる

うむ。

「User が持ちうるもの」という意味では一貫しているといえるけど、特定の目的のためという意味では不要なプロパティも多いよなぁ。

ここから先は文章が続き、本の要約にしかならないので、しばし飛ばします。

飛ばしつつ、気になったところを書き抜きます。

  • 特定の目的に特化して設計することで、変更に強い高品質な構造になる
  • モデルに目的外の要素が入り込んでいる場合、さらに見直す

思えば、自分も巨大モデルを作りたがる傾向がある気がします。

それで楽をしてきたと思うのですが、果たして本当に楽だっただろうか・・・?

うーん。

13.4 機能を左右するモデリング

  • 裏に隠れた真の目的を見破る
    • これは起こりうるトラブルを解決するための情報が揃えられるか?的な観点で説明してる
  • 機能性をイノベートする「深いモデル」
    • 目的・手段に応じた抽象化をする
    • 本質的課題を解決し、機能性の確信に貢献するモデル

14.1 リファクタリングの流れ

  • 外から見た挙動を変えずに、構造を整理すること
  • おっと、久々に骨のありそうなコードだ。書いてみよう。
  • なお、久々すぎてJavaのコードの読み方をすっかり忘れてしまっていた
  • リスト14.1相当のつもりで書き始めたけど、Mouse (使ってのオブジェクト指向)の書き方に従ってたらリスト14.5相当くらいのコードになってた。

ここクリックして展開

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

package Customer {
    use Mouse;

    has name => (
        is       => 'ro',
        isa      => 'Str',
        required => 1,
    );

    has id => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
    );

    has possession_point => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        default  => 1000,
    );

    sub is_enabled {
        return !!1;
    }

}

package Comic {
    use Mouse;

    has id => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
    );

    has current_purchase_point => (
        is      => 'ro',
        isa     => 'Int',
        default => 100,
    );

    sub is_enabled {
        return !!1;
    }

}

package PurchasePointPayment {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;
    use Time::Piece;

    # 購入者
    has customer => (
        is       => 'ro',
        isa      => 'Customer',
        required => 1,
        trigger  => sub {
            croak "有効な購入者ではありません。" unless $_[1]->is_enabled;
        }
    );

    # 購入するWebコミック
    has comic => (
        is       => 'ro',
        isa      => 'Comic',
        required => 1,
        trigger  => sub {
            croak "現在取扱できないコミックです。" unless $_[1]->is_enabled;
        }
    );

    # 購入日時
    has payment_date_time => (
        is       => 'ro',
        isa      => 'Time::Piece',
        required => 0,
        default  => sub {
            croak "所持ポイントが不足しています。"
              unless $_[0]->comic->current_purchase_point <=
              $_[0]->customer->possession_point;

            return localtime();
        }
    );

    __PACKAGE__->meta->make_immutable();
}

package main;

my $purchase_pointp_ayment = PurchasePointPayment->new(

    customer => Customer->new( name => 'sironekotoro', id => 1 ),
    comic    => Comic->new( id => 10 ),
);

  • ここからは以下の改修ポイントを実装していく。
  • unless $_[1]->is_enabled; でも良い気がするが、if $_[1]->is_disabled; にしておく
  • 購入日のプロパティでの残高チェックのサブルーチンを、customer に移す

で、改修したのがこちら。

しれっと use v5.36;, use signatures 使ってます。

ここクリックして展開

#!/usr/bin/env perl use strict; use warnings; use v5.36; use feature qw/say signatures/; package Customer { use Mouse; has name => ( is => 'ro', isa => 'Str', required => 1, ); has id => ( is => 'ro', isa => 'Int', required => 1, ); has possession_point => ( is => 'ro', isa => 'Int', required => 1, default => 1000, ); sub is_disabled { return !!0; } sub is_short_of_point ( $self, $comic ) { return !!1 if $self->possession_point <= $comic->current_purchase_point; } } package Comic { use Mouse; has id => ( is => 'ro', isa => 'Int', required => 1, ); has current_purchase_point => ( is => 'ro', isa => 'Int', default => 100, ); sub is_disabled { return !!0; } } package PurchasePointPayment { use Carp qw/croak/; use Mouse; use namespace::autoclean; use Time::Piece; # 購入者 has customer => ( is => 'ro', isa => 'Customer', required => 1, trigger => sub { my $customer = $_[1]; croak "有効な購入者ではありません。" if $customer->is_disabled; } ); # 購入するWebコミック has comic => ( is => 'ro', isa => 'Comic', required => 1, trigger => sub { my $comic = $_[1]; croak "現在取扱できないコミックです。" if $comic->is_disabled; } ); # 購入日時 has payment_date_time => ( is => 'ro', isa => 'Time::Piece', required => 0, default => sub { my $self = $_[0]; croak "所持ポイントが不足しています。" if $self->customer->is_short_of_point( $self->comic ); return localtime(); } ); __PACKAGE__->meta->make_immutable(); } package main; my $purchase_pointp_payment = PurchasePointPayment->new( customer => Customer->new( name => 'sironekotoro', id => 1 ), comic => Comic->new( id => 10 ), );

14.2 ユニットテストリファクタリングのミスを防ぐ

  • 悪魔を呼び寄せるような邪悪なコードには、テストコードが書かれていないことが多いです

ということで、リファクタリングをする前のコード書いていきます。

ここクリックして展開

#!/usr/bin/env perl
use strict;
use warnings;
use v5.36;
use feature qw/say signatures/;

package Product {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has price => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
    );
    __PACKAGE__->meta->make_immutable();
}

# 配送管理クラス
package DeliveryManager {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has products => (
        is       => 'ro',
        isa      => 'ArrayRef',
        required => 1,

        # trigger  => sub { croak '', unless ( $_[0] ) },
    );

    sub delivery_charge ($self) {
        my $charge      = 0;
        my $total_price = 0;

        for my $product ( @{ $self->products } ) {
            $total_price += $product->price;
        }

        if ( $total_price < 2000 ) {
            $charge = 500;
        }
        else {
            $charge = 0;
        }
        return $charge;

    }
    __PACKAGE__->meta->make_immutable();
}

package main;

my $deliverely_manager = DeliveryManager->new(
    products => [ Product->new( price => 100 ), Product->new( price => 200 ), ]
);

say $deliverely_manager->delivery_charge();

まず、Manager って名前が悪いよね。何でもメソッドを放り込まれる悪魔の名前だったような。

それはさておき、(この本の)リファクタリングの仕方を追っていきます。

まず、あるべき形を決めて、そこに旧来の DeliveryManager クラスから移植していきます。

  • 購入する商品一覧である、買い物かごクラス
# 買い物かご
package ShoppingCart {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has products => (
        is       => 'ro',
        isa      => 'ArrayRef[Product]',
        required => 0,
        default  => sub { [] },
    );

    sub add ( $self, $product ) {
        my @adding = @{ $self->products };
        push @adding, $product;
        return __PACKAGE__->new( products => \@adding );
    }

    __PACKAGE__->meta->make_immutable();
}
  • 商品クラス
package Product {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has id => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
    );

    has name => (
        is       => 'ro',
        isa      => 'Str',
        required => 1,
    );

    has price => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
    );
    __PACKAGE__->meta->make_immutable();
}
  • 配送料を計算するクラス
package DeliveryCharge {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has amount => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        default  => -1
    );
    __PACKAGE__->meta->make_immutable();
}

このあとにテストを書きます。

#!/usr/bin/env perl
use strict;
use warnings;

use Test::Simple tests => 2;
use lib qw(. ../);
use Product;
use ShoppingCart;
use DeliveryCharge;

{
    # 商品の合計額が2000円未満の場合、配送料は500円
    my $empty_cart = ShoppingCart->new();

    my $one_product_added =
      $empty_cart->add( Product->new( id => 1, name => '商品A', price => 500 ) );
    my $two_product_added =
      $one_product_added->add( Product->new( id => 2, name => '商品B', price => 1499 ) );

    my $charge = DeliveryCharge->new( shopping_cart => $two_product_added );

    ok( $charge->amount == 500, "商品の合計金額が2000円未満の場合、配送料は500円" );
}

{
    # 商品の合計額が2000円以上の場合、配送料は無料
    my $empty_cart = ShoppingCart->new();

    my $one_product_added =
      $empty_cart->add( Product->new( id => 1, name => '商品A', price => 500 ) );
    my $two_product_added =
      $one_product_added->add( Product->new( id => 2, name => '商品B', price => 1500 ) );

    my $charge = DeliveryCharge->new( shopping_cart => $two_product_added );

    ok( $charge->amount == 0, "商品の合計金額が2000円以上の場合、配送料は0円" );
}

この時点でのファイル構成はこんな感じ。

Perl では t というディレクトリの中に拡張子 t でテストファイルを作ります。

$ tree
.
├── DeliveryCharge.pm
├── Product.pm
├── ShoppingCart.pm
├── list_14_11.pl
└── t
    └── deliver_charge_test.t

で、t ディレクトリと同じ階層(t ディレクトリの中ではない)で prove コマンドを打つとテストを実行してくれます。

もちろん、t ディレクトリの中で perl deliver_charge_test.t でもok

$ prove
t/deliver_charge_test.t .. 1/2
#   Failed test '商品の合計金額が2000円未満の場合、配送料は500円'
#   at t/deliver_charge_test.t line 22.

#   Failed test '商品の合計金額が2000円以上の場合、配送料は0円'
#   at t/deliver_charge_test.t line 36.
# Looks like you failed 2 tests of 2.
t/deliver_charge_test.t .. Dubious, test returned 2 (wstat 512, 0x200)
Failed 2/2 subtests

Test Summary Report
-------------------
t/deliver_charge_test.t (Wstat: 512 (exited 2) Tests: 2 Failed: 2)
  Failed tests:  1-2
  Non-zero exit status: 2
Files=1, Tests=2,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.06 cusr  0.01 csys =  0.11 CPU)
Result: FAIL

はい、エラーが出ました。

今回は、というかテストファーストで進める場合は「あるべき構造」のガワを作り「あるべき応答」でテストをしたので、中身を作っていない以上エラーが出て当然です。

ここからリファクタリングをしていきます。

リスト14.13

まずはエラーを出さないように、最低限の実装です。

ここクリックして展開

package DeliveryCharge {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;
    use List::Util;

    has shopping_cart => (
        is       => 'ro',
        isa      => 'ShoppingCart',
        required => 1,
    );

    has amount => (
        is       => 'ro',
        isa      => 'Int',
        required => 0,
        builder  => "_build_amount",
    );

    sub _build_amount {
        my $self = shift;

        my $amount      = 0;
        my $total_price =
          $self->shopping_cart->products->[0]->price +
          $self->shopping_cart->products->[1]->price;

        if ( $total_price < 2000 ) {
            $amount = 500;
        }
        else {
            $amount = 0;
        }

        return $amount;
    }

    __PACKAGE__->meta->make_immutable();
}
1;

リスト14.14

このままだと、テストは通るけど、商品が2つまでしか入らないショッピングカートになってしまうので、ちゃんと書きます。

ここクリックして展開

package DeliveryCharge {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;
    use List::Util;

    has shopping_cart => (
        is       => 'ro',
        isa      => 'ShoppingCart',
        required => 1,
    );

    has amount => (
        is       => 'ro',
        isa      => 'Int',
        required => 0,
        builder  => "_build_amount",
    );

    sub _build_amount {
        my $self = shift;

        my $amount      = 0;
        my $total_price = 0;

        for my $product ( @{ $self->shopping_cart->products } ) {
            $total_price += $product->price;
        }

        if ( $total_price < 2000 ) {
            $amount = 500;
        }
        else {
            $amount = 0;
        }

        return $amount;
    }

    __PACKAGE__->meta->make_immutable();
}
1;

今は配送料を扱う DeliveryCharge クラス内で、商品の合計を出して配送料を決めている。

本来、商品の合計を出すのは ShoppingCart クラスのはず。

ということで、リファクタリングしたのがこちらです!

ここクリックして展開

package ShoppingCart {
    use v5.36;
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;

    has products => (
        is       => 'ro',
        isa      => 'ArrayRef[Product]',
        required => 0,
        default  => sub { [] },
    );

    sub add ( $self, $product ) {
        my @adding = @{ $self->products };
        push @adding, $product;
        return __PACKAGE__->new( products => \@adding );
    }

    sub total_price {
        my $self   = shift;
        my $amount = 0;

        for my $product ( @{ $self->products } ) {
            $amount += $product->price;
        }

        return $amount;
    }

    __PACKAGE__->meta->make_immutable();
}

1;
package DeliveryCharge {
    use Carp qw/croak/;
    use Mouse;
    use namespace::autoclean;
    use List::Util;
    use lib qw/./;
    use ShoppingCart;

    use constant {
        CHARGE_FREE_THRESHOLD => 2000,
        PAY_CHARGE            => 500,
        CHARGE_FREE           => 0,
    };

    has shopping_cart => (
        is       => 'ro',
        isa      => 'ShoppingCart',
        required => 1,
    );

    has amount => (
        is       => 'ro',
        isa      => 'Int',
        required => 0,
        builder  => "_builder_amount",
    );

    sub _builder_amount {
        my $self = shift;
        my $amount =
          $self->shopping_cart->total_price() < CHARGE_FREE_THRESHOLD
          ? PAY_CHARGE
          : CHARGE_FREE;
    }

    __PACKAGE__->meta->make_immutable();

}
1;

いやー、正直ここの部分(リスト14.14〜リスト14.19)は本に買いてあるコードを追っても全然わからず混乱。

結局、最後のリファクタリングが終わった後のコードを見て、何をするべきなのかを理解したという感じです。

このあとはリファクタリングする時に気をつける点として「機能追加とリファクタリングを同時に行わない」とか、あやふやな仕様を理解するための分析手法として「仕様化テスト」「思考リファクタリング」といった手法が紹介されています。

「良いコード/悪いコードで学ぶ設計入門」第11章 コメント 〜 12章 メソッド

久々に

経理の仕事において、四半期決算ってのは本当に大変なお仕事で、ほぼ1ヶ月持っていかれます。

翌月はそのリカバリでボーッとしているという感じでした。

(これはどうにかして負荷を軽減したいところ・・・)

その余波で「良いコード/悪いコードで学ぶ設計入門」を読み進めるのもすっかり止まってしまい、また書き方もだいぶ忘れてしまった・・・んですが、こういう時にちゃんと学習記録を書き残してあると安心ですね。

11章はコメントってことで、コード少なめ文章多めという感じです。

コードも Perl で書き直すほどのものではないという感じなので、サクサク読んでいきます。

11章 コメント

11.1 退化コメント

  • 情報が古くなり、実装を正しく説明しなくなったコメントを退化コメントという
  • コメントは(コードの)劣化コピーにすぎないので、意図が通じるクラス名の命名などが必要
  • ロジックの挙動をなぞるだけのコメントは退化しやすい

11.2 コメントで命名をごまかす

  • すごい文字数のメソッドが出てくる
    • 書き写したくない
  • メソッドの可読性を上げるとで、説明のコメントが不要になる

11.3 意図や仕様変更時の注意点を読み手に伝えること

  • 意図や指標変更時の注意点をコメントしよう

11.4 コメントルールのまとめ

11.5 ドキュメントコメント

  • ドキュメントコメント?って思ったけど、Perl だったら POD 、JavaScript だったら JSDoc みたいなものを言うらしい。知らんかった

12章 メソッド

12.1 必ず自身のクラスのインスタンス変数を使うこと

  • 例外もあるが、原則は「自身のクラスのインスタンス変数を使う」

  • 完全コンストラクタパターンを用いて、コンストラクタにガード節を用意する

  • 他のクラスのインスタンス変数を変更するメソッドを作らない
    • 変更したいんんスタンス変数を持つクラスにメソッドを実装する

12.2 不変をベースに予期せぬ動作を防ぐ関数にすること

  • オブジェクトのプロパティ書き換えるときも新しいプロパティ作ってそれを返す、みたいな感じかな

12.3 尋ねるな、命じろ

  • あるクラスがよそのクラスの状態を判断したり、状態に応じてよその値を変更したりするのは低凝集構造
  • getter / setter が多用されているコードはその状況になりやすい
  • メソッドの呼び出し側で複雑な処理をするのはでなく、呼び出される側で制御をするよう設計する

12.4 コマンド・クエリ分離

  • 初めて聞く
  • メソッドはコマンド(変更)、またはクエリ(問い合わせ)のどちらか一方だけを行うよう設計する
  • コマンドとクエリを同時に行うメソッド種別をモディファイアという
    • 知らんかった
    • モディファイアはなるべく避ける
  • そろそろコード書きたくなってきたので書いてみる
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

use Function::Parameters;

fun gain_and_get_point($point){
    my $point += 10;
    return $point;
}

# モディファイア
my $origina_point = 0;
my $modifier = gain_and_get_point($origina_point);
say $modifier;  # 10

# コマンド・クエリ分離
fun gain_point($point){
    $point += 10
}

fun gat_point($point){
    return $point;
}

my $gain_pint = gain_point($origina_point);
say gat_point($gain_pint);  # 10

12.5 引数

  • 引数は不変にすること
  • フラグ引数は使わない
    • 切り替え機構はストラテジパターンを利用する
  • null を渡さない
    • 例)未装備状態を null ではなく、 Eqipment.EMPTY で表現する
  • 出力引数を使わない
  • 引数は限りなく少なくする

12.6 戻り値

  • 型を使って戻り値の意図を表明すること
    • プリミティブ型を使わず、独自の型を使って戻り値の意図を明確に表明する
  • 引数に null を渡さないように、null を返さない
  • エラーは戻り値で返さず、例外にする

と、12章はここまで。次の13章も文章が多い感。

macOS Monterey で IO::Socket::SSL, Net::SSLeay のインストールに失敗する

何度目かの環境再構築中

色々あって、業務で利用している Macbook Pro 2017 をリカバリすることにしました。

ディスクユーティリティで SSD を一旦まっさらに。

そこに macOS Monterey をネットワーク経由でクリーンインストール

ちょうど経理の月初作業も終わり、お盆休みもあったので落ち着いて環境構築を進めることができました。

自作モジュールのインストールでエラー

この自作モジュールは業務を楽にするために自分で作ったもので、以下のような動きをします。

  1. Google Sheet を csv にしてダウンロード
  2. ダウンロードした csv から数値を抜き出す
  3. 抜き出した数字に応じて画像生成

この "Google Sheet を csv にしてダウンロード" の時に(透過的に)利用するモジュールが IO::Socket::SSL です。 文字通り、SSL通信に関係するモジュールです。

これのインストールに失敗しました。

$ cpanm IO::Socket::SSL
--> Working on IO::Socket::SSL
Fetching http://www.cpan.org/authors/id/S/SU/SULLR/IO-Socket-SSL-2.074.tar.gz ... OK
==> Found dependencies: Net::SSLeay
--> Working on Net::SSLeay
Fetching http://www.cpan.org/authors/id/C/CH/CHRISN/Net-SSLeay-1.92.tar.gz ... OK
Configuring Net-SSLeay-1.92 ... N/A
! Configure failed for Net-SSLeay-1.92. See /Users/sironekotoro/.cpanm/work/1660879857.14507/build.log for details.
! Installing the dependencies failed: Module 'Net::SSLeay' is not installed
! Bailing out the installation for IO-Socket-SSL-2.074.

IO::Socket::SSL が依存している Net::SSLeay のインストールに失敗してるようです。 ログはここに出力されているので確認。

! Configure failed for Net-SSLeay-1.92. See /Users/sironekotoro/.cpanm/work/1660879857.14507/build.log for details.

失敗時のログはこんな感じ。

cpanm (App::cpanminus) 1.7046 on perl 5.036000 built for darwin-2level
Work directory is /Users/sironekotoro/.cpanm/work/1660879879.14561
You have make /usr/bin/make
You have /usr/local/bin/wget
You have /usr/bin/tar: bsdtar 3.5.1 - libarchive 3.5.1 zlib/1.2.11 liblzma/5.0.5 bz2lib/1.0.8 
You have /usr/bin/unzip
Searching Net::SSLeay () on cpanmetadb ...
--> Working on Net::SSLeay
Fetching http://www.cpan.org/authors/id/C/CH/CHRISN/Net-SSLeay-1.92.tar.gz
-> OK
Unpacking Net-SSLeay-1.92.tar.gz
Entering Net-SSLeay-1.92
Checking configure dependencies from META.json
Checking if you have ExtUtils::MakeMaker 6.58 ... Yes (7.64)
Checking if you have English 0 ... Yes (1.11)
Checking if you have constant 0 ... Yes (1.33)
Checking if you have File::Spec::Functions 0 ... Yes (3.84)
Checking if you have Text::Wrap 0 ... Yes (2021.0814)
Configuring Net-SSLeay-1.92
Running Makefile.PL
Do you want to run external tests?
These tests *will* *fail* if you do not have network connectivity. [n] n
*** Be sure to use the same compiler and options to compile your OpenSSL, perl,
    and Net::SSLeay. Mixing and matching compilers is not supported.

******************************************************************************
* COULD NOT FIND LIBSSL HEADERS                                              *
*                                                                            *
* The libssl header files are required to build Net-SSLeay, but they are     *
* missing from /usr. They would typically reside in /usr/include/openssl.    *
******************************************************************************
-> N/A
-> FAIL Configure failed for Net-SSLeay-1.92. See /Users/sironekotoro/.cpanm/work/1660879879.14561/build.log for details.

まぁ、原因はわかっていて。

macOS Monterey が採用している SSL のライブラリは LibraSSL というものです。

$ openssl version
LibreSSL 2.8.3

対して、IO::Socket::SSL が要求しているのは OpenSSL というライブラリです。

エラーログのここですね。

******************************************************************************
* COULD NOT FIND LIBSSL HEADERS                                              *
*                                                                            *
* The libssl header files are required to build Net-SSLeay, but they are     *
* missing from /usr. They would typically reside in /usr/include/openssl.    *

ということで、macでお馴染みのパッケージマネージャ Homebrew で openSSL をインストールします。

$ brew install openssl

openSSL のインストール後、無事 IO::Socket::SSL のインストールが完了。

めでたしめでたし。

なぜこれを書いたのか

何度目かのインストールで毎回やってるなぁ・・・と思ったのと、割と MacPerl やってて引っかかりやすいところかなぁ、と思ったので。

autossh を macOS 起動時に実行する

macOS 起動時に autossh を実行したい

autossh という ssh のラッパーソフトがあり、sshが切れたら自動的に繋ぎ直してくれるという便利なやつです。

autossh は homebrew で入れられます。

これを ターミナル起動時 ではなく、macOS の起動時に自動実行させたいという要望です。

まずは普通にターミナルから実行できるか試す

うちの場合はこんな感じでした。

$ ssh -L 127.0.0.1:${LOCAL_PORT}:${SERVER_IP}:${SERVICE_PORT} -i ~/ssh/id_rsa -l ${LOGIN_NAME} -p ${SERVER_PORT} ${SERVER_ID}

ローカルポートにアクセスすると、サーバーの特定のサービスポートにつながる、いわゆるトンネルを掘るというやつです。

これを autossh に書き直すとこんな感じ。

${LOCAL_PORT} とかは 実際には 22 などのポート番号、${LOGIN_NAME} には sironekotoro とかの文字列が入っております。

$ autossh -M 0 -f -N -L 127.0.0.1:${LOCAL_PORT}:${SERVER_IP}:${SERVICE_PORT} -i ~/ssh/id_rsa -l ${USERNAME} -p ${SERVER_PORT} ${SERVER_ID}

これをターミナルから実行して、ちゃんとトンネルが掘れたことを確認。

これを launchd の作法で書くだけで大丈夫なはず!

launchd で動かせない

大丈夫じゃなかった。ダメでした。

もう、何がダメかわからないレベル。

これが動かなかった plist です。供養。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>local.mxcl.autossh</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin</string>
    </dict>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/autossh</string>
        <string>-M</string>
        <string>0</string>
        <string>-f</string>
        <string>-N</string>
        <string>-p</string>
        <string>${SERVER_PORT}</string>
        <string>-l</string>
        <string>${LOGIN_NAME}</string>
        <string>-i</string>
        <string>/Users/${USERNAME}/.ssh/id_rsa</string>
        <string>-L</string>
        <string>127.0.0.1:${LOCAL_PORT}:${SERVER_IP}:${SERVICE_PORT}</string>
        <string>${SERVER_IP}</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/${USERNAME}/Desktop/standard_out.txt</string>
    <key>StandardErrorPath</key>
    <string>/Users/${USERNAME}/Desktop/standard_error.txt</string>
  </dict>
</plist>

エラーを吐き出すようにしてるけど、standard_error.txt には、autossh を引数なし(あるいは不正な引数)時の usage が出力されてるのみ。

で、最小限の引数でってことでバージョンを表示する -V オプションだけを入れると、ちゃんと standard_out.txt にバージョン番号が追記される。

ぐぐぐぐ。

    <array>
        <string>-V</string> 
    </array>

なお、launchd ではシェル変数とかホームディレクトリを表す ~ とかが展開されないってことに気づくのにえらい時間がかかった。

基本に立ち返る

いやいや、俺は致命的な勘違いをしているのかも。

よくやるじゃん致命的な勘違い。

ってことで、ファイルを作るだけの簡単なやつで試してみる。

$ cd ~/Library/LaunchAgents
$ touch local.test.touch.plist

作った local.test.touch.plist は以下の内容で編集する

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>local.test.touch</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/touch</string>
        <string>/Users/sironekotoro/Desktop/test.txt</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/sironekotoro/Desktop/standard_out.txt</string>
    <key>StandardErrorPath</key>
    <string>/Users/sironekotoro/Desktop/standard_error.txt</string>
  </dict>
</plist>

作ったら、文法に間違いないか確認する

$ plutil -lint local.test.touch.plist
local.test.touch.plist: OK

ではいざ実行!

$ launchctl load local.test.touch.plist

これでうまく動いて、デスクトップ上に text.txt という空のファイルができた。

うん、間違ってない・・・となると何故 autossh は動かないのか。

とりあえず、後片付け。

$ launchctl unload local.test.touch.plist
$ rm local.test.touch.plist

ワークアラウンド

いいかげん、あれこれ試すのも飽きてきたので、諦めてワークアラウンド(回避策)さがす。

Autometer でコマンド実行するやつを作って、それを 環境設定 → ユーザとグループ のログイン項目に入れる

Autometer 単体ではちゃんと動いた。

でも、macOS 起動時になぜか、なぜか、Xcode が立ち上がる。なんで???

autossh のコマンド入れたシェルクスリプト作って、それを launchd から呼ぶ

これはうまくいきました。

autossh.sh というファイルを以下の内容で作って

#!/bin/bash
/usr/local/bin/autossh -M 0 -f -N -L 127.0.0.1:${LOCAL_PORT}:${SERVER_IP}:${SERVICE_PORT} -i ~/ssh/id_rsa -l ${LOGIN_NAME} -p ${SERVER_PORT} ${SERVER_ID}

実行権限つけておきます。

$ chmod +x autossh.sh

このファイルを起動する plist を作ります。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>local.mxcl.autossh</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin</string>
    </dict>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/${USERNAME}/autossh.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/sironekotoro/Desktop/standard_out.txt</string>
    <key>StandardErrorPath</key>
    <string>/Users/sironekotoro/Desktop/standard_error.txt</string>
  </dict>
</plist>

あとはさっきの touch の時のように起動時のリストに登録・・・これで動きました。

本当は、シェルスクリプト通さずに plist の中身だけで autossh を実行したかったんですが、今はこれが精一杯ってことで。

お疲れ様でした。

「良いコード/悪いコードで学ぶ設計入門」第10章 #ミノ駆動本

第9章から文字比率が高くなり、そのまま書き出すと文章を書写するだけになってしまう・・・ということで、小見出し&ときどき感想って形式で。

目的駆動名前設計

名前から目的や意図が読み取れることを特徴とします。

10.1 悪魔を呼び寄せる名前

例として挙げられているのは「商品」。

「予約」「注文」「出品」「発送」それらのロジックが入り込みやすい名称になってしまう。

これらを関心事ごとに分離し、隔離する。

関心が密結合になっているものを、疎結合高凝集とし、分離し、関心事に相応しい名称に変更する。

・・・ここは例示がめっちゃ鮮やかで、うちに出来るんかな?

となったところです。

まぁ、最初っから正解に辿り着けるものでもないので、意識しながら、良いコードに近づいていきたいですね。

関心の分離には、ビジネス目的を名前として表現することがポイントになります。

この辺りをヒントにやっていきましょう。

10.2 名前を設計する - 目的駆動名前設計

ここは筆者さんが先に重要ポイントをまとめてくれているので、引用します

  • 可能な限り具体的で、意味範囲が狭い、特化した名前を選ぶ
  • 存在ベースではなく、目的ベースで名前を考える
  • どんな関心ごとがあるか分析する
  • 声に出して話してみる
  • 利用規約を読んでみる
  • 違う名前に置き換えられないか検討する
  • 疎結合高凝集になっているか点検する

10.3 設計時の注意すべきリスク

  • 名前無頓着になるな
  • 仕様変更時の「意味範囲の変化」に警戒
  • 会話には登場するのにコード上に登場しない名前に注意
    • 会話に登場する重要な概念が、ソースコード上で名前もつけられず、雑多なロジックの中に埋没していることが本当に頻繁に見受けられます。

  • 形容詞で区別が必要な時はクラス化のチャンス

10.4 意図がわからない名前

  • tmp とか foo とか baz とかを業務コードで使わない
  • 技術駆動命名
    • int とか str おか memory とか flag とか value01 とか
    • ビジネス目的を指し示す命名に直す
  • ロジック構造をなぞった名前
    • isMemberHpMoreThenZeroAndIsMemberCanActAndIsMemberMpMoreThanMagicCostMp

    • 邪悪すぎるメソッド名
      • 業務のコードでこんなの見たら叫ぶと思う
    • クラス内で、早期リターンのif文 * 3 で書き直されてた
  • 驚き最小の原則
    • メソッド名から類推できないコードを書かない
    • ロジックの意図と名前を一致させる

10.5 構造を大きく歪ませてしまう名前

  • データクラスに陥る名前

    • データだけではなく、ちゃんとメソッドも同じクラスに設定しよう
    • ただし、DTO(Data Transfer Object)のような、データ転送用途に使われる(データしか入っていないオブジェクトを用いる)設計パターンもある
  • Manager, Processr、Controller などの、意味が広すぎる名前は用いない

  • もっと具体的な、意味の狭い概念を見つけていく

  • 連番連名

10.6 名前的に居場所が不自然なメソッド

  • 「動詞 + 目的語」のメソッド名に注意

    • consumeMagicpoint
    • addiemToParty
    • 関心に無関係なメソッドは「動詞+目的語」の形になる傾向がある
  • 可能な限り動詞1語で済む名前にする

  • 不適切な居場所の boolean メソッド

    • boolean 型を返すメソッドは 「Class名 is メソッド名」とした時に自然かどうかを考える
    • This is member ins hungry

  • 名前の省略

    • しない

10.7 名前の省略

(2022/08/27 追記)

  • 意図がわからなくなる省略はしない

  • 基本的に名前は省略しない

  • 省略をどう判断するか

    • 意味が失われていないか?
    • 問題が生じていないか?
    • プログラム言語ごとの習慣に合致しているか?
    • 命名方法がチームで一致しているか?