私は2020年になりましたが、あなたはまだ単にif-elseを使っていますか?

10867 ワード

上級言語では、基本的にif-elseおよびswitch-caseのような条件文が提供されており、皆が判断するのに便利である。私達はプログラムを書く時、常に2つ以上の実行パスを指定して、プログラムを実行する時、その中の1つのパスを選択して、対応する語句を実行して、対応する結果を生みます。これも条件語句のプログラムにおける作用です。
if-elseの例
皆さんはC言語を初めて学ぶ時に、毎月の日数を出力するプログラムを書いたはずです。

//C      
int Days(int months, int years){
	int days;
	if(months==1 || months==3 || months==5 || months==7 || months==8 || months==10 || months==12){
		days=31;
	}else if(months==2){
		if((years%4==0 && years%100!=0) || years%400==0){
			days=29;
		}else{
			days=28;
		}
	}else if(months==4 || months==6 || months==9 || months==11){
		days=30;
	}else{
		printf("    !     :
"); Days(months,years); } return days; }
このプログラムは「耳慣れています」というが、後から見ると「煩わしい」と感じるようになり、多層のif-elseの入れ子は可読性を低下させるだけでなく、プログラムの効率にも大きく影響します。
if-elseの問題
以上から、if-else判定文は非常に簡単に使用されているが、やや複雑な論理シーンでは、if-elseを頻繁に使用すると、プロジェクト全体の可読性とメンテナンス性が大幅に低下してしまうことがわかっている。
私達は考えてみてもいいです。もしプロジェクトに新しい状況ができたら、元のコードに基づいてif-elseを追加し続けます。しかし、需要は減りません。このように悪循環していくと、いくつかのif-elseがいくつかのバージョンを更新して数十個になってしまうかもしれません。
(もちろん、今はあなたの会社にハードな要求があるかもしれません。またはテンプレートを開発したら、おめでとうございます。)
デザインモードの観点から、if-elseはまさに「悪い」コードのすべてを持っています。
  • データと論理結合を実現する
  • 拡張トラブル、メンテナンス性が低い
  • 改善if-else
    if-elseはすべての代替が必要なわけではないです。正確に言うと、今はそれを改善し続け、彼の運行をスムーズにするしかないです。
    短絡符号と三元表現
    先日、私たちはまた群の中でこの二つを言いました。短絡記号、また「論理演算子」とも言います。簡単な場面では、if-else(特にいくつかの条件を同時に満たす必要がある)の代わりにそれを使ってもいいです。
    例えばこれは――一つの数が2のべき乗かどうかを判断します。
    
    //c++    
    class Solution {
    public:
      bool isPowerOfTwo(int n) {
      	//       2       ,              1,     0 ,
      	//          1   ,        ,    0          1,
      	//         ,     0
        return (n > 0) && (!(n & (n - 1)));
      } 
    };
    if-elseの代わりに3元の記号を使うこともできます。ほとんど最適な計算機判定記号です。特に、多条件複合判定(層ネスト層)に適用されます。ただし、多くの3つの要素演算子は、コードの可読性に影響を与えやすいことに注意したい。
    例えば――判断n!結果の端数のゼロの数:
    
    //java    
    public class Solution {
      public int trailingZeroes(int n) {
      	//    
        return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
      }
    }
    もちろん、私たちはもう一つの改良方法があります。各条件の下でコードロジックが多いなら、前もってジャンプして関数を終了することも考えられます。これはforサイクルを参考にしました。
    switch-caseを教えてください
    Switch-caseは言語自体が提供しているもう一つの条件文です。ifと本質的には区別がありません。コードはもっと簡潔に見えるだけです。例えば――年齢を判断する:
    
    goodswitch(age){
    	case 10:
    		break;
    	case 20:
    		break;
    	case 30:
    		break;
    	//...
    }
    しかし、switch-caseは複数の類似条件を根本的に解決することができません。
    表駆動法
    これは筆者が最も推奨する書き方です。大データ量の判断、範囲の違いなどの問題に解決策があります。
    もう一回文章の冒頭の問題を見てみましょう。出力は毎月何日間ありますか?
    考えを変えてもいいです。毎月は数字に対応していますが、月は順番に並べています。だから、一列で日数を保存して、次の標的で訪問してもいいですか?
    
    //javascript     
    const month=new Date().getMonth(),
    	year=new Date().getFullYear(),
    	isLeapYear=year%4==0 && year%100!=0 || year%400==0;
    
    const monthDays=[31,isLeapYear ? 29 : 28,31,30,31,30,31,31,30,31,30,31];
    const days=monthDays[month];
    えっと、このコードは動きやすくなります。少なくともこのように見えます。
    また上で年齢を判断するコードもあります。
    
    //JavaScript     
    ages=[10,20,...];
    funs=['a1','a2',...];
    for(let i in ages){
    	if(age==ages[i]){
    		funs[i]();
    	}
    }
    function a1(){
    }
    function a2(){
    }
    //...
    二つの例を見ましたが、「表駆動法」について知っていると思います。
    表ドライバとは、論理文を使わずにテーブルから情報を検索するプログラムです。実際には、論理文で選択できるものは、チェックシートで選ぶことができます。簡単な場合は論理文を使ったほうが分かりやすくなります。しかし、論理鎖がますます複雑になるにつれて、時計法はますます魅力的になってきた。コード大全」
    表を使って駆動するのはif-elseのように「楽」ではないかもしれません。まず二つの問題を考える必要があります。
    どのようにテーブルからデータを調べますか?if-elseが範囲だと判断したら、どうやって調べますか?何を調べますかデータ?インデックス?)
    この二つの問題に基づいて、テーブルによって駆動されるクエリを3つに分けます。
    直接アクセスインデックスアクセスステップアクセス
    1、直接訪問表
    最近は母の「意思」で保険会社に行ってみましたが、この保険料率は年齢、性別、婚姻状態などによって変わります。上記の出力日付のプログラムを見て考えてみてください。論理制御解(if or switch)で異なる料金を表したら、どれぐらいかかりますか?(実は、あなたのコードはタコのようになるかもしれません。
    ここの「年齢」は範囲です。配列やオブジェクトでマッピングできませんでした。これには二つの解決策があります。直接にテーブルまたは階段アクセス表にアクセスします。まず「直接訪問表」を試してみて、二つの方法を見つけました。
    メッセージをコピーして直接キーの値を使うことができます。私たちは1~17歳までの年齢ごとに1つの情報をコピーして、直接にageで訪問します。他の年齢にも同じです。この方法は操作が簡単で、表の構造も簡単であることにあります。しかし、スペースを浪費するという欠点があります。つまり、多くの冗長情報が生成されます。キーの値を変えることを勧めません。年齢範囲をキーに変えるなら?このように直接に訪問することができます。唯一考慮しなければならない問題はいくつかの状況の下で年齢がどのようにキーに変えられますか?
    第二の方法については、「if-else変換も必要ですか?」もちろんです。前にも述べましたが、簡単なif-elseには問題がありません。表ドライバは複雑な論理判断を最適化するために、より柔軟に、拡大しやすいようにします。
    
    //TypeScript     
    const Age={
    	0:"unadult",
    	1:"adult"
    }
    const Gender={
    	0:"female",
    	1:"male"
    }
    const Marry={
    	0:"unmarried",
    	1:"married"
    }
    
    const rateMap={
    	[Age[0]+Gender[0]+Marry[0]]:0.1,
    	[Age[0]+Gender[0]+Marry[1]]:0.2,
    	[Age[0]+Gender[1]+Marry[1]]:0.3,
    	[Age[0]+Gender[1]+Marry[0]]:0.4,
    	[Age[1]+Gender[0]+Marry[0]]:0.5,
    	[Age[1]+Gender[0]+Marry[1]]:0.6,
    	[Age[1]+Gender[1]+Marry[1]]:0.7,
    	[Age[1]+Gender[1]+Marry[0]]:0.8
    }
    const isAdult=(age:number)=>age>=18 ? 1: 0
    const getDate=(age,hasMarried,gender)=>{
    	age=isAdult(age)
    	return rateMap[Age[age]+Gender[gender]+Marry[marry]]
    }
    これこそ正しい開き方ですね。
    はい、先ほども一つの方法を言ったようです。
    2、階段アクセス表
    同じように上の年齢範囲の問題を解決するために、階段訪問はインデックスなしで直接訪問しますが、より省スペースになります。
    階段を使うためには、区間ごとの上限を表に記入して、年齢のある区間を循環してチェックする必要がありますので、階段を使って訪問する時は区間の端点を確認してください。
    
    //TypeScript     
    const ageRanges:number[]=[17,65,100],
    	keys:string[]=['<18','18-65','>65'];
    const getKey=(age:number):string=>{
    	for(let i in keys){
    		//console.log(i);
    		//console.log(ageRanges[i]);
    		if(age<=ageRanges[i]){
    			return keys[i];
    		}
    	}
    	return keys[keys.length-1];
    }
    3、インデックスアクセステーブル
    実際の保険料率の問題は、年齢の範囲を処理する時に頭が痛くなります。この範囲は上記のように簡単に「key」が得られないです。
    私たちは情報をコピーして直接キーを使うことができると言っていましたが、この方法は多くの空間を浪費しました。年齢ごとにデータを保存しています。
    しかし、私たちが索引を保存するだけなら、この索引でデータを調べますか?
    人が生まれたばかりが0歳で、最大100歳まで生きると仮定すると、私たちは長さ101の配列を作成し、配列の下には人の年齢に対応しています。このように0-17の各年齢には「18」を保存し、18-65には「18-65」を保存し、65以上には「65」を保存します。このように私達は年齢を通じて対応する索引を得て、索引を通じて対応するデータを調べます。
    この方法は上の直接アクセステーブルより複雑に見えるが、キーを変換することが難しく、データが空間的に大きい場面でインデックスを使ってアクセスしてみてもいいです。
    
    //Typescript     
    const ages:string[]=['<18','<18','<18',...'18-65','18-65','18-65',...'>65','>65','>65',...'>65'];
    const ageKey:string=ages[age];
    このように表を作る時はちょっと面倒ですが、データを処理する時はとても簡単です。
    表駆動の典型的な応用
    表駆動の最大の意味は、条件判断(データ)と論理を分離し、条件を配置可能なテーブル(オブジェクトor配列)で管理することです。
    0-360°を8つの異なる空間に分割しますが、if-elseで実現しないでください。
    
    //JavaScript     
    const keys=['A','B','C','D','E','F','G','H'],
    	range=[45,90,135,180,225,270,315,360];
    const degreeTkey=(rage)=>{
    	for(let i in range){
    		if(rage<=range[i]){
    			return keys[i];
    		}
    	}
    }
    const map={
    	'A':()=>{
    		//...
    	},
    	'B':()=>{
    		//...
    	},
    	//...
    }
    
    //   :
    map[degreeTkey(46)]();
    if-else対応関係の複雑な問題を列挙して解決します。
    どのキャラが何をしていますか?これは明らかな対応関係です。だから習った「列挙」はなぜ使われないですか?
    実は、列挙と上記の「表検索」は似ています。「システム管理者操作権限」の問題を挙げます。
    まず、共通インターフェースRoleOperationを定義し、異なるキャラクターができる操作を表します。
    
    public interface RoleOperation {
      String op();//           op  
    }
    次に、私たちは異なるキャラクターの状況をすべてエニュメレート・クラスに任せ、異なるキャラクターが異なる権限を持つエニュメレート・クラスを定義する。
    
    public enum RoleEnum implements Role0peration {
      //     ( A    )
    	ROLE_ ROOT_ _ADMIN {
    		@Override
    		public String op() {
    			return "ROLE_ ROOT_ ADMIN:" + " has AAA permission";
    		}
    	},
    	//     ( B    )
    	ROLE_ ORDER_ ADMIN {
    		@override
    		public String op() {
    			return "ROLE_ ORDER_ _ADMIN:" + " has BBB permission";
    		}
    	},
    	//    ( C    )
    	ROLE_ NORMAL {
    	@Override
    		public String op() {
    			return "ROLE_ NORMAL:" + "has CCC permission";
    		}
    	};
    }
    それに、これから条件を拡充したいなら、エニュメレート・クラスにコードを入れればいいです。以前のコードを変えに行くのではなくて、これは安定していませんか?
    
    public class JudgeRole {
    	public String judge( String roleName ) {
    		//      !   if/else  !
    		return RoleEnum.va1ue0f(roleName).op();
    	}
    }
    工場モード解決if-else「分岐過多」問題
    支店によって違うことをすると、明らかに工場のパターンを使うきっかけを提供します。私達は状況を単独で定義して、工場種類に集まっていけばいいです。
    まず、異なるキャラクターに対して、その業務クラスを単独で定義することができます。
    
    //     ( A    )
    public class RootAdminRole implements Role0peration {
    	private String roleName ;
    	public RootAdminRole( String roleName){
    		this.roleName = roleName ;
    	}
    	@Override
    	public String op() {
    		return roleName + "has AAA permission" ;
    	}
    }
    
    //     ( B    )
    public class OrderAdminRole implements RoleOperation {
    	private String roleName ;
    	public OrderAdminRole( String roleName ) {
    		this.roleName = roleName ;
    	} 
    	@Override
    	public String op() {
    		return roleName + "has BBB permission";
    	}
    }
    
    //    ( C    )
    public class NormalRole implements RoleOperation {
    	private String roleName ;
    	public NormalRole( String roleName){
    		this.roleName = roleName;
    	}
    	@Override
    	public String op() {
    		return roleName + "has CCC permission";
    	}
    }
    次にもう一つの工場類RoleEnumが上記の異なる役割を集約することを書きます。
    
    public class RoleFactory {
    	static Map<String, Role0peration> roleOperationMap = new HashMap<>();
    	//                
    	static {
    		role0perationMap.put( "ROLE_ ROOT_ ADMIN", new RootAdminRole("ROLE_ _ROOT_ ADMIN") ) :
    		roleOperationMap.put( "ROLE_ ORDER_ ADMIN", new OrderAdminRole("ROLE_ ORDER_ ADMIN") );
    		role0perationMap.put( "ROLE_ NORMAL", new NormalRole("ROLE_ NORMAL") );
    	}
    	pub1ic static RoleOperation getOp( String roleName ) {
    		return roleOperationMap.get( roleName ) ;
    	}
    }
    次は上の工場を借りて、業務コードの呼び出しも一行のコードだけでいいです。if/elseも同様に消去されました。
    
    public class JudgeRole {
    	public String judge(String roleName){
    		//      !     if/else   !
    		return RoleFactory.get0p(roleName).op();
    	}
    }
    これからは条件を拡張するのも簡単です。新しいコードを追加するだけで、以前の業務コードを動かす必要がなく、「開閉原則」にぴったりです。
    ここで、私達は2020年になりました。どのようにして、単にif-elseの文章を使ってここに紹介しましたか?if-elseの使用内容に関しては、以前の文章を検索してください。また、次の関連記事を見てください。これからもよろしくお願いします。