日 | 月 | 火 | 水 | 木 | 金 | 土 |
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
文字サイズ変更:
トップページ > Perlについて
●Perlについて●
2023-02-06 20:48:50良いコードについて:2023-02-3
良いコードについて:2023-02-3.txt
さて、プログラミングでの良いコードについての記事を見つけました。
それはPythonで書かれたコードなので、Perlに書き直して、記事と合わせて載せたいと思います。
良いコードについて:2023-02-2の続きになります。
関連した処理をクラス側で定義する
もう少し突っ込んで考えてみると、税込みの計算$shina{"food_$t"} * ( 1 + $shina{"tax_food_1"});は、純粋にItemクラスのデータしか利用していません。
ということは、Itemクラスが担当しても良さそうですね。あるデータに関連した処理は、そのデータの近くに配置する方が管理しやすいでしょう。
これを踏まえて、再度実装すると、こうなります。
# total_price3.pm
package total_price3;
use strict;
sub new {
my $class = shift;
my $self = {
my $price => my $total_price
};
return bless $self,$class;
}
sub item {
my @item_list = @_;
my %items;
my $i = 0;
my $monoflg = 0;
foreach(@item_list){
if($_ =~ /food/){ # food
$monoflg = 1;
}elsif($monoflg == 1){
$items{"food_$i"} = $_; # 100yen
$items{"tax_food_1"} = 0.08; # 税率8%
$monoflg = 0;
$i++;
}elsif($_ =~ /item/){ #item
$monoflg = 2;
}elsif($monoflg == 2){
$items{"item_$i"} = $_; # 200yen
$items{"tax_item_1"} = 0.1; # 税率10%
$monoflg = 0;
$i++;
}
}
return \%items;
sub price_with_tax {
my $s_ref = shift;
my %shina = %{$s_ref};
my $t = 0;
my $price = 0;
my $total_price = 0;
foreach(sort keys %shina){
if($_ =~ /food_$t/ ){
$price = $shina{"food_$t"} * ( 1 + $shina{"tax_food_1"}); # foodは8%消費税
}elsif($_ =~ /item_$t/ ){
$price = $shina{"item_$t"} * ( 1 + $shina{"tax_item_1"}); # itemは10%消費税
}
$total_price = $total_price + $price;
$price = 0;
if($shina{"food_$t"} ne ''){ $t++; }
}
return $total_price;
}
}
sub total_price {
my $hashref = shift;
my %shina = %{$hashref};
return &price_with_tax(\%shina);
}
my @syohin_list = ('アンパン','food',100,'アンパン','food',100,'ペン','item',200);
my $item_ref = &item(@syohin_list);
my %syohin_list2 = %{$item_ref};
my $price = &total_price(\%syohin_list2);
print "Total:$price yen\n";
1;
※カンマ(,)を全角で表示しています。
Itemクラスにprice_with_taxメソッドを定義し、total_priceサブルーチンでそれを呼び出しています。
こうするとtotal_priceサブルーチンはすべてのitemにprice_with_taxメソッドを呼び出して、合計するだけのサブルーチンになったので、内包表記を利用して書くようにしました。
クラスを整理する
total_priceサブルーチンとしては、これで十分に責務が整理されていそうです。
ただ、まだもう少し責務の分離を進めてみましょう。
責務的な観点でいうと、今度はItemクラスが少し気になります。
というのも、商品の品種によってtax_rateを決定していますが、商品の品種の種類が増えれば、このコンストラクタを修正しなくてはいけません。
Itemクラスを変更するということは、基本的にはItemクラスを利用するすべてのコードがその変更の影響を受けるということです。
例えば、商品の品種に新しくbookという種別ができたとして(税率は…、0.01くらいにしましょうか。
本は人類にとってとても重要な商品ですもんね)、Itemクラスのコンストラクタに分岐を1つ加えるだけです。
ですが、そのせいでバグを混入させてしまうかもしれません。
bookの種別が出てきていない既存コードに影響を与えてしまうかもしれません。
このような仕様追加に対して、既存コードがなるべく影響を受けないようにするにはどうすればいいでしょうか?
そんなうまい方法があるでしょうか?
実は、これは継承を利用するとうまく扱うことができます。
つまり、商品の品種ごとにItemクラスを継承した子クラスを定義していく方法です。
これを実装するとこうなります。
# total_price4.pm
package total_price4;
use strict;
sub new {
my $class = shift;
my $self = {
'total_price' => my $total_price
};
return bless $self, $class;
}
sub item {
my @item_list = @_;
my %items;
my $i = 0;
my $monoflg = 0;
foreach(@item_list){
if($_ =~ /food/){ # food
$monoflg = 1;
}elsif($monoflg == 1){
$items{"food_$i"} = $_; # 100yen
$monoflg = 0;
$i++;
}elsif($_ =~ /item/){ #item
$monoflg = 2;
}elsif($monoflg == 2){
$items{"item_$i"} = $_; # 200yen
$monoflg = 0;
$i++;
}
}
return \%items;
sub tax_rate_defult {
return 0.1;
}
sub price_with_tax {
my $s_ref = shift;
my %shina = %{$s_ref};
my $t = 0;
my $price = 0;
my $total_price = 0;
foreach(sort keys %shina){
if($_ =~ /food_$t/ ){
$price = $shina{"food_$t"} * ( 1 + &food()); # foodは8%消費税
}elsif($_ =~ /item_$t/ ){
$price = $shina{"item_$t"} * ( 1 + &tax_rate_defult()); # itemは10%消費税
}
$total_price = $total_price + $price;
$price = 0;
if($shina{"food_$t"} ne ''){ $t++; }
}
return $total_price;
}
}
sub food {
return 0.08;
}
sub book {
return 0.01;
}
sub total_price {
my $hashref = shift;
my %shina = %{$hashref};
return &price_with_tax(\%shina);
}
my @syohin_list = ('アンパン','food',100,'アンパン','food',100,'ペン','item',200);
my $item_ref = &item(@syohin_list);
my %syohin_list2 = %{$item_ref};
my $price = &total_price(\%syohin_list2);
print "Total:$price yen\n";
1;
※カンマ(,)を全角で表示しています。
また、tax_rateフィールドをやめて、その代わりtax_rateメソッドを追加しています。
ただし、税率はクラスで固定になっています。
これで、新しい種別が来たとしても、新しい子クラスを定義するだけです。
例えばbookに対しては、クラスを追加するだけでよく、既存のFoodクラスやItemクラスを変更していません。
ですので、既存のコードに影響を与えることなく新しい仕様を追加することができています。
ここまで、責務を分離させることを指針としてコードを変更してきました。一番最初のコードからすると随分と異なるコードになりましたね。
整理すると、サブルーチンから「ロジック」と「具体的な値でロジックに適用する」という処理を分離するために、引数を利用しました。
次に、データをまとめるという責務を分離させるために、抽象データ型としてのクラスを導入しました。
このように、責務をうまく分離・整理するためにプログラミング言語の機能を使っていくことが重要です。
ちなみに、責務がまったく分離されていない最初のコードも、今回限りの利用であれば、それで十分です。
ですが、今後使う必要があるのであれば、何らかの責務の分離が必要になります。ただ、どこまで分離・整理するのが良いのかは状況次第ではあります。
例えば、もう絶対に種別は増えないということがわかっていれば、継承を利用してFoodクラスやBookクラスを作ることは過剰です。
実際には、現在の求められる要件や想定などから、その都度、どの程度まで責務の分離・整理が必要かを判断していってください。
サンプルコードを使いながら、責務の分離を進めていきましたが、そもそもなぜ責務の分離が重要なのでしょうか?
これは先ほどの責務の分離を行う際にも色々と触れているのですが、変更を容易に行うために責務の分離が重要なのです。
まず、責務が分離・整理されていないコードは変更が難しいという問題があります。
責務が整理されていないと、どこを変更すればよいのかを判断するのが難しくなります。
例えば、先ほどの例のように、total_priceサブルーチンが引数に対応していない場合、似たようなサブルーチンを大量に書く必要があるかもしれません。
すると、至る所に「税込みの合計」の責務を持つコードが散らばることになり、例えば税率に変更が入った場合など、どこを変更すれば正しく変更できたことになるのか、判断するのがとても難しくなります。
このように、責務が散らばっていないということは重要で、そのためにクラスやサブルーチンなどを利用して責務を整理する必要性が生じます。
さらに、total_priceサブルーチンの例で言うと、最初、total_priceサブルーチン自身が、商品の種別ごとに税率を管理し、税込み金額の合計を算出していました。
その後、最終的には、total_priceサブルーチンの責務は「税込み金額の合計を算出する」にまでまとめられました。
つまり、途中のtotal_priceサブルーチンには責務が複数あったというわけです。
1つのコンポーネント(関数やクラスなど)に複数の責務があるのは基本的には好ましい状況ではありません。
責務に変更が出てしまうと、それごとに修正する必要が出てきます。
つまり、責務が多ければ多いほど、そのコンポーネントは変更される可能性が高くなってしまいます。
変更というのは常に正しく実装できるものではありません。
当然、バグが混入する危険があります。
また、複数の責務が混在していると、無関係な責務のコードをうっかり変更してしまうかもしれません。
少なくともそのような可能性は排除できません。
これに対して、1つのコンポーネントに1つの責務しかないのであれば、関係のない責務によってコードを変更する必要はなくなり、変更による影響の範囲を限定できます。
また、責務が分離されているとテストがしやすいという点も重要です。
テストはコードが正しく動作することを保証する行為ですが、責務が分離されていないとテストが複雑になりやすいのです。
テストを行うとしてもどの責務のテストを行っているのかがわかりにくくなりますし、無関係な責務のためにテストデータなどを準備することが必要になったりします。
ですが、責務が分離され、その責務のロジックだけが抽出されていると、そのロジックだけを狙い撃ちにして確認することができます。
記事はここまで。
無理やり、Perlに書き換えた感が拭えませんが、いかがでしたでしょうか。
学ぶ一助になれば、幸いです。
出典「日経クロステック 変更が難しいコードはNG、プログラミングでは「責務の分離」を意識すべし 3page」
出典「日経クロステック 変更が難しいコードはNG、プログラミングでは「責務の分離」を意識すべし 4page」
Print
Twitter(test)
short URL
いいね:79 back to the TOP |
良いコードについて:2023-02-2
さて、プログラミングでの良いコードについての記事を見つけました。
それはPythonで書かれたコードなので、Perlに書き直して、記事と合わせて載せたいと思います。
良いコードについて:2023-02-1の続きになります。
このサブルーチンを詳しく見ると実は「税込み合計」を算出する以外の責務を持っていることがわかります。
それが「商品の品種と価格の紐付け」です。
こういうデータを紐付ける責務は、具体的なデータを知っている側(つまり、このサブルーチンの利用者)が担当するべきです。
複数のデータを紐付ける、あるいは1つのデータにする方法は色々ありますが、今回は抽象データ型が利用できそうです。
PythonやJavaScriptではクラスの機能になります。
※Perlもクラスの機能を持たせることができます。
# total_price.pm
package total_price;
use strict;
sub new {
my $class = shift;
my $self = {
my $price => my $total_price
};
return bless $self,$class;
}
sub item {
my @item_list = @_;
my %items;
my $i = 0;
my $monoflg = 0;
foreach(@item_list){
if($_ =~ /food/){ # food
$monoflg = 1;
}elsif($monoflg == 1){
$items{"food_$i"} = $_; # 100yen
$monoflg = 0;
}elsif($_ =~ /item/){ #item
$monoflg = 2;
}elsif($monoflg == 2){
$items{"item_$i"} = $_; # 200yen
$monoflg = 0;
}
$i++;
}
return \%items;
}
sub total_price {
my $hashref = shift;
my %shina = %{$hashref};
my $price = 0;
my $total_price = 0;
foreach(sort keys %shina){
if($_ =~ /food/ ){
$price = $shina{$_} * 1.08; # foodは8%消費税
}elsif($_ =~ /item/ ){
$price = $shina{$_} * 1.1; # itemは10%消費税
}
$total_price = $total_price + $price;
$price = 0;
}
return $total_price;
}
my @syohin_list = ('アンパン','food',100,'アンパン','food',100,'ペン','item',200);
my $item_ref = &item(@syohin_list);
my %syohin_list2 = %{$item_ref};
my $price = &total_price(\%syohin_list2);
print "Total:$price yen
";
1;
※カンマ(,)を全角で表示しています。
抽象データ型としてItemクラスを導入しました。
total_priceサブルーチンは、そのItemクラスのインスタンスのリストを受け取るように修正しました。
仲の良いデータをまとめる
状況によってはこれで完成でもいいんですが、せっかくなのでもう少し責務を整理してみましょう。
今のtotal_priceサブルーチンで気になるのは、商品の品種に"food"とそれ以外の種類があることを知っていることです。具体的には以下の3つが気になります。
1.商品の品種が文字列であることを、total_priceサブルーチンは知っている。
2.商品の品種ごとの税率を知っているのがtotal_priceサブルーチンである。
3.商品の品種として、食べ物とそれ以外の2種類しかないということをtotal_priceサブルーチンが知っている。
(1)に関してですが、商品の品種が文字列であるかどうかはItemクラスの事情であって本質的にこのtotal_priceサブルーチンが知るべきことではないはずです。
より重要なのは、(2)と(3)です。
(2)のポイントは、商品の品種ごとに税率が変わるということは、そもそも商品の品種と税率が仲の良いデータであるということです。
商品の品種を管理するのはItemクラスで、税率を決めているのがtotal_priceサブルーチンです。ということは、Itemクラスとtotal_priceサブルーチンがとても仲が良いということになります。
これはあまり良くありません。2つのコンポーネント(クラスやサブルーチン)の仲が良すぎる関係は、どちらかに変更が入ると、もう一方にも修正が必要になるかもしれないということです。
これに対応するには、仲の良いデータ同士を1つにまとめることです。
つまり、税率をItemクラス内に持たせるべきで、Itemインスタンスが商品の品種ごとに適切な税率を返してくれれば良さそうです。
(3)も似たような問題です。total_priceサブルーチンがItemクラスの商品の品種のすべてを知っているのは、仲が良いということです。
これも解消しなくてはいけません。
そもそも、total_priceサブルーチンが商品の品種を管理しているのは税率のためでした。
ということは、税率データをItemクラスに移せば、(3)も解消できそうです。
# total_price2.pm
package total_price2;
use strict;
sub new {
my $class = shift;
my $self = {
my $price => my $total_price
};
return bless $self,$class;
}
sub item {
my @item_list = @_;
my %items;
my $i = 0;
my $monoflg = 0;
foreach(@item_list){
if($_ =~ /food/){ # food
$monoflg = 1;
}elsif($monoflg == 1){
$items{"food_$i"} = $_; # 100yen
$items{"tax_food_1"} = 0.08; # tax_rate:税率8%
$monoflg = 0;
$i++;
}elsif($_ =~ /item/){ #item
$monoflg = 2;
}elsif($monoflg == 2){
$items{"item_$i"} = $_; # 200yen
$items{"tax_item_1"} = 0.1; # tax_rate:税率10%
$monoflg = 0;
$i++;
}
}
return \%items;
}
sub total_price {
my $hashref = shift;
my %shina = %{$hashref};
my $t = 0;
my $price = 0;
my $total_price = 0;
foreach(sort keys %shina){
if($_ =~ /food_$t/ ){
$price = $shina{"food_$t"} * ( 1 + $shina{"tax_food_1"}); # foodは8%消費税
}elsif($_ =~ /item_$t/ ){
$price = $shina{"item_$t"} * ( 1 + $shina{"tax_item_1"}); # itemは10%消費税
}
$total_price = $total_price + $price;
$price = 0;
if($shina{"food_$t"} ne ''){ $t++; }
}
return $total_price;
}
my @syohin_list = ('アンパン','food',100,'アンパン','food',100,'ペン','item',200);
my $item_ref = &item(@syohin_list);
my %syohin_list2 = %{$item_ref};
my $price = &total_price(\%syohin_list2);
print "Total:$price yen
";
1;
※カンマ(,)を全角で表示しています。
クラス定義が少し長くなりましたが、Itemクラス内でtax_rateを管理するようにしました。
Itemクラスのコンストラクタ内で、商品の品種に応じてtax_rateを決定するようにしましたが、これはItemクラスが商品の品種の全種類を管理することになります。
Itemクラスの責務としては自然ですね。
まだ続きます。
出典「日経クロステック 変更が難しいコードはNG、プログラミングでは「責務の分離」を意識すべし」
Print
Twitter(test)
short URL
いいね:82 back to the TOP |
良いコードについて:2023-02-1
さて、プログラミングでの良いコードについての記事を見つけました。
それはPythonで書かれたコードなので、Perlに書き直して、記事と合わせて載せたいと思います。
良いコードがどういうものか、というのはとても難しい問題です。様々な良さの観点があり、絶対的に良いと言えるようなコードがあるわけではありません。
しかし、良いコードには少なくとも満たしている性質がいくつか知られています。
その中の1つが、責務が整理されていることです。責務は、コード上における役割のようなものです。関心事と言われたりもします。
責務を考えるために、例えば、買い物での税込みの合計金額の算出方法を考えてみましょう。
簡単にするために、食品は消費税が8%、それ以外は10%にします。
また、小数点以下の計算は今回気にしないでいいことにしましょう。
ここで「100円のアンパンを2個、200円のペンを1本買ったら、税込みの合計はいくらか」という問題を考えてみます。
これを安直に実装するとこうなります。
# test1.pl
use strict;
my $price = &total_price();
print "Total:$price
";
sub total_price {
my $total_price = 100 * 2 * 1.08; # アンパン(2個)+消費税8%
$total_price += 200 * 1 * 1.1; # ペン(1個)+消費税10%
return $total_price;
}
total_priceサブルーチンを定義して、このコードで「100円のアンパンを2個、200円のペンを1本」を計算し、436円という値を算出しています。
ですが、これは今回限りのコードになってしまっていますね。
当然ですが、別の商品を買う度にtotal_priceサブルーチンに修正が入ってしまいます。
この実装は「税込み合計を計算する」と、「アンパン2個とペン1本の場合にその計算を行う」という2つの処理を、total_priceサブルーチンに押し込めていると言えます。
このため、商品が変われば、total_priceサブルーチンに影響が出てしまいます。あるいは、total_priceサブルーチンが具体値を知りすぎている、つまり抽象化が足りないとも言えます。
ではtotal_priceサブルーチンから、具体的な値を分離しましょう。ここで使えるのが引数ですね。
具体的な値を知る代わりに、仮引数を使って計算を行うことができます。愚直に書くとこのような実装になるかと思います。
# test2.pl
use strict;
my %syohin;
$syohin{"food1"} = 100; # アンパン1個100円
$syohin{"food2"} = 100; # アンパン1個100円
$syohin{"item1"} = 200; # ペン1個200円
my $price = &total_price(\%syohin);
print "Total:$price yen
";
sub total_price {
my $hashref = shift;
my %shina = %{$hashref};
my $total_price = 0;
my $price = 0;
foreach (keys %shina){
if($_ =~ /food/ ){
$price = $shina{$_} * 1.08; # foodは8%消費税
}elsif($_ =~ /item/ ){
$price = $shina{$_} * 1.1; # itemは10%消費税
}
$total_price = $total_price + $price;
}
return $total_price;
}
total_priceサブルーチンが随分と書き換わりました。
%syohinが商品の価格のリストで、$_が商品種別です。
これで新しい商品(例えば、カレーパン)が来たとしても、変化するのは引数の中身であって、サブルーチンを修正する必要がなくなりました。
責務の観点で言うと、total_priceサブルーチンが「税込み合計を計算する」という責務により集中できるようになったと言えます。
まだ続きます。
出典「日経クロステック 変更が難しいコードはNG、プログラミングでは「責務の分離」を意識すべし」
Print
Twitter(test)
short URL
いいね:15 back to the TOP |