全知全能を目指す人のありがたい雑記

何かしら意味のあるありがた~い話か、意味のない雑念だらけの日記を書く予定です。

【パワー系PG】変数を連番で命名はNG!配列やコレクションを使おう

あなたのプログラム、「変数を連番で命名」してませんか?

まさかいないとは信じたいけれど。。

けれども、
某質問サイトのユーザーから頂いたコードを拝見した結果…
存在した。存在してしまった!
(しかも意外に多い…)

先輩や知人、友人等に教わる人がいなかったのだろう。
少し可哀想だと思い、記事を作った。

変数を連番で命名しちゃっている方は、
今からでも遅くない!
しっかり覚えて、修正していこう。

今回は「五十音を取り扱うプログラム」を題材にした。

以下を出力するプログラム

あいうえお
かきくけこ
さしすせそ
たちつてと
なにぬねの
はひふへほ
まみむめも
やゆよ
らりるれろ
わおん

コレクション(List等)を使いたいので書きやすいC#で書きます。

まず駄目コードから。

パワー系PGのコード

using System;

public class GojuonProgram{
    public static void Main()
    {
        string value1;
        string value2;
        string value3;
        string value4;
        string value5;
        string value6;
        string value7;
        string value8;
        string value9;
        string value10;
        
        value1 = "あいうえお";
        value2 = "かきくけこ";
        value3 = "さしすせそ";
        value4 = "たちつてと";
        value5 = "なにぬねの";
        value6 = "はひふへほ";
        value7 = "まみむめも";
        value8 = "やゆよ";
        value9 = "らりるれろ";
        value10 = "わおん";
        
        Console.WriteLine(value1);
        Console.WriteLine(value2);
        Console.WriteLine(value3);
        Console.WriteLine(value4);
        Console.WriteLine(value5);
        Console.WriteLine(value6);
        Console.WriteLine(value7);
        Console.WriteLine(value8);
        Console.WriteLine(value9);
        Console.WriteLine(value10);
    }
}

パワーを感じるぞ。。
書くの疲れた。。

突っ込みたいのは何点か

  • valueが何なのか変数名だけ見てるとわからん
  • や行を「やいゆえよ」にしたいとき変数を一々上から見ていかないといけない

とか、色々ある。
はい、じゃあ配列を使おう。

配列を使った実装

using System;

public class GojuonProgram{
    public static void Main()
    {
        string[] gojuon = new string[]
        {
            "あいうえお",
            "かきくけこ",
            "さしすせそ",
            "たちつてと",
            "なにぬねの",
            "はひふへほ",
            "まみむめも",
            "やゆよ",
            "らりるれろ",
            "わをん"
        };
        
        for(int line = 0; line < gojuon.Length; ++line)
        {
            Console.WriteLine(gojuon[line]);
        }
    }
}

大分楽になったね。
これならやゆよはgojuonっていう配列1個を修正すればいいってすぐに分かる。

個人的には配列の要素0が「あ行」、要素1が「か行」って言う
作りがあんまり直感的じゃなくて嫌だなぁと思ってる。

コレクション(List)を使った実装

using System;
using System.Collections.Generic;

public class GojuonProgram{
    public static void Main()
    {
        List<string> gojuon = new List<string>
        {
            "あいうえお",
            "かきくけこ",
            "さしすせそ",
            "たちつてと",
            "なにぬねの",
            "はひふへほ",
            "まみむめも",
            "やゆよ",
            "らりるれろ",
            "わをん"
        };
        
        foreach(var line in gojuon)
        {
            Console.WriteLine(line);
        }
    }
}

配列と似たような作りに見えるね。

ただ、配列とリストは似ているようで、ぜんぜん違う。
データの構造が違う。

↓が参考になるかな。
qiita.com

大雑把に言うと
配列は要素数が固定。
リストは要素数が可変。

今回のプログラムでは
gojuonというデータに対して
"きゃきゅきょ"や"しゃしゅしょ"、"ちゃちゅちょ"なども
追加する想定であれば、リストを使うべき。

そうでなければ、
ランダムアクセスが早い(=処理速度に影響する)ので、
配列を使うべき。

↑に書いたとおり、
配列の要素数は「固定」なので、
宣言時にしか要素数を指定できない。

配列を大きさの違う配列に移すことは可能だが、
頻繁に移すようならコレクションを使おう。

あと、C#のクラスだが、自分ならDictionaryを使いたい。

コレクション(Dictionary)を使った実装(とLINQを少々。。)

using System;
using System.Collections.Generic;
using System.Linq;

public class GojuonProgram{
    public static void Main(){
        Dictionary<string, string> gojuon = new Dictionary<string, string>();
        
        gojuon.Add("あ行","あいうえお");
        gojuon.Add("か行","かきくけこ");
        gojuon.Add("さ行","さしすせそ");
        gojuon.Add("た行","たちつてと");
        gojuon.Add("な行","なにぬねの");
        gojuon.Add("は行","はひふへほ");
        gojuon.Add("ま行","まみむめも");
        gojuon.Add("や行","やゆよ");
        gojuon.Add("ら行","らりるれろ");
        gojuon.Add("わ行","わをん");
        
        Console.WriteLine("******キーと値を交互出力******");
        foreach(var line in gojuon)
        {
            Console.WriteLine(line.Key);
            Console.WriteLine(line.Value);
        }
        
        Console.WriteLine("******キーを元に値を表示******");
        Console.WriteLine(gojuon["さ行"]); //さ行の文字列がほしい!
        Console.WriteLine(gojuon["わ行"]); 
        
        Console.WriteLine("******五十音の中で文字列のサイズが5の行を抽出し出力******");
        foreach(var line in gojuon.Values.Where(v => v.Length == 5))
        {
            Console.WriteLine(line);
        }
        
        
    }
}

出力

******キーと値を交互出力******
あ行
あいうえお
か行
かきくけこ
さ行
さしすせそ
た行
たちつてと
な行
なにぬねの
は行
はひふへほ
ま行
まみむめも
や行
やゆよ
ら行
らりるれろ
わ行
わをん
******キーを元に値を表示******
さしすせそ
わをん
******五十音の中で文字列のサイズが5の行を抽出し出力******
あいうえお
かきくけこ
さしすせそ
たちつてと
なにぬねの
はひふへほ
まみむめも
らりるれろ

便利な機能は逐次利用していこう。

まとめ

  • 変数を連番に命名するようなら
  • 似たような意味を持つ変数を使っているようなら

配列やコレクションを利用しよう。

【パワー系PG】データを整数型で表現しないで列挙型を使え

あなたのプログラム、「intを不必要に多用」してませんか?

どの言語にもenumみたいな列挙型ってあると思うんだ。
C言語における、intとenumによる実装の違いを紹介しよう。

intを使ったときの実装

#include <stdio.h>

int main(void){
    
    int warningRate; //2桁入力【例:36】
    scanf("%02d", &warningRate);
    
    int signal;
    
    if(warningRate < 50){
        signal= 0;
    }
    else if(warningRate < 75){
        signal= 1;
    }
    else{
        signal= 2;
    }
    
    switch(signal){
        case 0:
            printf("緑、今日も平和だ");
            break;
        case 1:
            printf("黄、危ない予感");
            break;
        case 2:
            printf("赤、逃げろー!");
            break;
    }
}

読ませる側や保守・運用を考えない
パワー系PGの臭いがする!!

enumを使ったときの実装

#include <stdio.h>

enum Signal{
    Green,
    Yellow,
    Red,
};

int main(void){
    
    int warningRate; //2桁入力【例:36】
    scanf("%02d", &warningRate);
    
    enum Signal s;
    
    if(warningRate < 50){
        s = Green;
    }
    else if(warningRate < 75){
        s = Yellow;
    }
    else{
        s = Red;
    }
    
    switch(s){
        case Green:
            printf("緑、今日も平和だ");
            break;
        case Yellow:
            printf("黄、危ない予感");
            break;
        case Red:
            printf("赤、逃げろー!");
            break;
    }
}

何が言いたいか

int signal;って変数に
0とか1とか2って入れてるけど、
signalが0だと何なの?signalが1って何?2って?

見ただけじゃあ判断ができない。
0や1や2である意味は、
後続の処理から推測するしか方法がない。

何でもかんでもintやstringで解決する輩が一定多数いるので
気になって仕方が無い。

あと、余談だがenum⇔intの相互変換もできる。

以上

enumっていう数値を名前付き定数として
表現できるものがあるんだから使おうぜ~。。

【パワー系PG】ハードコーディングはやめてDBMSを使いたい

あなたのプログラム、「ハードコーディング」してませんか?

ハードコーディングとは簡単に言うと以下のような感じ
内容としては業務データをプログラムで作っている処理。

int input;
scanf("%d", &employeeNo);

if(employeeNo< 1&& employeeNo> 100){
    //①の業務データ
}
else if(employeeNo< 101 && employeeNo> 200){
    //②の業務データ
}
else if(employeeNo< 201 && employeeNo> 300){
    //③の業務データ
}
else if(employeeNo< 301 && employeeNo> 400){
    //④の業務データ
}
else if(employeeNo< 401 && employeeNo> 500){
    //⑤の業務データ
}
else if(employeeNo< 501 && employeeNo> 600){
    //⑥の業務データ
}
else if(employeeNo< 601 && employeeNo> 700){
    //⑦の業務データ
}
else if(employeeNo< 701 && employeeNo> 800){
    //⑧の業務データ
}

あっ、パワー系PGが書いたコードか!!!

さて、上のコードで気になるのが何点か。

  • 1とか100とか101とか200って何ぞ??
  • コード量にパワーを感じる

コードを書いていない側からすると
色々気になってくるコードだね。

卒業する方法

  • 1とか100とか101とか200って何ぞ??
  • コード量にパワーを感じる

仰る通りです。
パワー感じますよね。
1とか100とか101とか、パッと見、数値の意味がわからんちんですな。

そもそも、
業務データを取り扱うときは
プログラムにガリガリ書くべきではない
です。
まあ、当たり前の話です。

業務データを取り扱うときは
プログラムに直接持たせるのではなく、
DBMS(データベース管理システム)を使いましょう。

言語とDBMSを連携するには相応のライブラリや
フレームワークを流用するのが筋
です。
DBMSも、色々種類があるので何か自分に合うものを探すと良いです。

例えば「C言語MySqlの連携」を例に挙げるなら、
以下の記事辺りを参考にしてください。
d.hatena.ne.jp

ぐぐればいくらでも出ます。

あと、データベースにアクセスするためには
SQLというのを覚えないといけません。

まあ単純な文なら一瞬で覚えられます。
cfm-art.sakura.ne.jp

書き方のイメージとして以下のような形式。

SELECT [営業所種別] FROM [従業員テーブル] WHERE [従業員番号]

SQL文はライブラリやフレームワークによって
プログラム内に記載できます。

例えば、WHEREの後には条件を記載するのですが、
条件に先ほど定義したemployeeNoを渡すこともできます。
※その場合はemployeeNoに対するレコードをDBからもらえます。

ですので、
データベースから引っこ抜いた営業所種別のレコードをそのまま流用すれば
プログラムとしては数行で済むわけです。

業務データはDBMSを使おう。

「パワー系PG」は卒業したい

こんにちは。

突然ですが、あなたのプログラムからパワーは感じませんか?

  • 1ファイルに数千行も記載してる?
  • 機能分割がきちんとできていない?
  • 同じコードをコピペしている?
  • 変数を連番に命名している?
  • 変数名が適当?
  • 1行に無理やりロジックを詰め込む?
  • 似たようなリテラルを直書きしてる?

さて、本来はプログラムにパワー(気合)を込めるべきなのでしょうか?

自分はそうは思いません。

成果物に対して、
可能な限り少ないコード量で人が読めるコーディングをすべきです。

気合で書いたコードは以下の問題があります。

  • バグが発生したとき、何千行もあるコードを人が気合で見ることになる
  • バグを修正するとき該当箇所が複数箇所にコピペされており、修正箇所の網羅と修正漏れが怖い
  • 適当な変数名を定義したりリテラルを直書きしたりで、該当行で何の処理をしているのかまったくわからない
  • 1行にやりたいことを詰めすぎて処理が追えない

適当に箇条書きしましたが、とにかく恐ろしいです。

このように、頭を使わないで
とにかく「コピペ」や「一時的な解決にしかならないコーディング」等、
コーディングをパワープレイで済ませようとする人のことを
パワー系PG」と呼んでます。

「パワー系PG」はまともなPGではありません。

自分のプログラムにパワーを感じてきたら
要注意です。

パワー系PGは卒業して、
効率を重視する面倒くさがり屋で優秀なPGになろう。

C言語でうるう年計算・月の末日計算・曜日計算を一通り書いた

学生の課題あるあるのプログラム。

各関数は全部落ちてるソースをパクっただけ。

fudebaco.com
edu.clipper.co.jp
C言語入門:うるう年判定プログラム:Geekなぺーじ

#include <stdio.h>

int main(void){
    int year; //yyyyで入力する!例「1970」
    scanf("%d", &year);
    
    int month; //mmで入力する!例「01」
    scanf("%02d", &month);
    
    char weekdays[7][4] = {"日","月","火","水","木","金","土"};
    
    int lastday = GetLastDay(year, month);
    
    for(int today = 1; today < lastday + 1; ++today){
        printf("%02d%02d%02d日 ",year,month,today);
        int weekday = GetWeekday(year, month, today);
        printf("%s曜日\n",weekdays[weekday]);
    }
    return 0;
}

int IsLeapYear(int year){
    if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) {
      printf("%02d年はうるう年\n", year);
      return 1;
    } else {
      printf("%02d年はうるう年ではない\n", year);
      return 0;
    }
}

int GetWeekday( int y, int m, int d ){
    if( m < 3 ) {
        y--; m += 12;
    }
    return ( y + y/4 - y/100 + y/400 + ( 13*m + 8 )/5 + d )%7;
}

int GetLastDay(int year, int month){
    int l;
    int lmdays[] = {31,29,31,30,31,30,31,31,30,31,30,31};
    int mdays[]  = {31,28,31,30,31,30,31,31,30,31,30,31};
    int lastday;
    
    l = IsLeapYear(year);
    if (l == 1) {
        lastday = lmdays[month - 1];
    } else {
        lastday = mdays[month - 1];
    }
    
    printf("%02d%02d月の末日は%d\n",year,month,lastday);
    return lastday;
}

入力

1992 02

出力

1992年はうるう年
1992年02月の末日は29日
1992年02月01日 土曜日
1992年02月02日 日曜日
1992年02月03日 月曜日
1992年02月04日 火曜日
1992年02月05日 水曜日
1992年02月06日 木曜日
1992年02月07日 金曜日
1992年02月08日 土曜日
1992年02月09日 日曜日
1992年02月10日 月曜日
1992年02月11日 火曜日
1992年02月12日 水曜日
1992年02月13日 木曜日
1992年02月14日 金曜日
1992年02月15日 土曜日
1992年02月16日 日曜日
1992年02月17日 月曜日
1992年02月18日 火曜日
1992年02月19日 水曜日
1992年02月20日 木曜日
1992年02月21日 金曜日
1992年02月22日 土曜日
1992年02月23日 日曜日
1992年02月24日 月曜日
1992年02月25日 火曜日
1992年02月26日 水曜日
1992年02月27日 木曜日
1992年02月28日 金曜日
1992年02月29日 土曜日

テーマをカスタマイズした

背景色が地味だったのと、透明感を持たせたいなーと言うことで
コンテンツのアルファ値を変更しやした!!

デザインCSSに以下の記述を追加。

#container-inner{
  background-color: rgba(46, 46, 46, 0.93);
}

.entry-inner blockquote{
  background-color: rgba(46, 46, 46, 0);
}

引用が追随して透過されなかったので、
entry-innerクラスの引用文に対して直接透過を指定しやした。

透明感がでてすっきりしたぜ。

C#で文字列を弄るなら正規表現を使いたい

ファイル名に拡張子をつけていないので、
ファイルを保存する際に、
拡張子を追記して保存するようにしたい

と仰る方を見かけたので一つサンプルコードを。

文字列操作なんて不要!
なら、正規表現で置き換えてみよう。

↓無理やり文字列の後尾に「.png」を追加するプログラム

using System;
using System.Text.RegularExpressions;

public class Program{
    public static void Main(){

        string fileName = Console.ReadLine();

        if(fileName == null){
            Console.WriteLine("入力に文字を入れてください。");
            return;
        }
        
        if(fileName.Contains(".")){
            Console.WriteLine("拡張子を.pngに置き換えます");
            fileName = Regex.Replace(fileName,@"(?<=\.)(.*)","png");
        }
        else{
            fileName = string.Concat(fileName , ".png");
        }
        
        Console.WriteLine(fileName);
    }
}

追記

↑のやつ
「aaaa.hoge.fuga」ってやると
「aaaa.png」になる。駄目コード。

ってか、少し調べてたらこんなクラスがあったで。。。
Path.ChangeExtension メソッド (String, String) (System.IO)

正規表現なんて不要!
なら、ChangeExtensionで置き換えてみよう。

↓無理やり文字列の後尾に「.png」を追加するプログラム(Ver2)

using System;

public class Program{
    public static void Main(){
        
        string fileName = Console.ReadLine();
        
        if(fileName == null){
            Console.WriteLine("入力に文字を入れてください。");
            return;
        }
        
        fileName = System.IO.Path.ChangeExtension(fileName, "png");
        
        Console.WriteLine(fileName);
    }
}

え?
1行で済むやないかーーーーーい

.NET様様だね。