FC2ブログ

おじいコード

Qiitaにこんな記事が上がった。
https://qiita.com/yuu_j/items/d6e6fc4476ab1dc35cdc
こんなコードは嫌だ、おじいコード駆逐したい(とりあえず9つ)

コメント欄が繁盛しているがw、概ねその通り。

転職してきた若いプログラマが変なコード書いている。
どうやら前社の社内研修で教わったとのこと。
さて、老害は何を教えたのだろうか。


いや全くもってその通り。
  1. 変数名が雑
    IDE使ってるなら別にどうだって良いのだろうけれど、スペルミスは勘弁して欲しい。
    datasとかやめてよと。それならdtの方がましだよと。
    年寄り的解説:変数名の長さに制約があった時代の名残りとか、スペル調べる環境がない現場でそれっぽくするための便法だったり。今となっては必要がないだろう。
  2. 無駄な省略
    これも別にいいんだけど、countはcountと書けよと何度思ったことか。
    年寄り的解説:変数名の長さに制約が(略
  3. 無駄に()つけている
    処理系によっていろいろあるのは理解できるが、少なくともC/C++では評価順序が決まっているので不要な箇所で()つけられると理解に困る場合がある。所によっては不要な()を強制することも。
    「return の戻り値には必ず()をつけるべし」ってとこ多いでしょ。やめてよと。
    年寄り的解説:C/C++のごく初期には関数マクロが多用されていたこともあり、誤動作を避けるため何でもかんでも括弧つけるのが推奨された場面もあり。
  4. 変更するときに昔のコードをコメントアウトして残す
    これはルールとして明文化されているベンダーも多いが、邪魔なだけ&嘘だらけ。
    今時こんなのは、「ソース管理されていない」ってことで評価落とすだけだから若いお方はやめるように。
    同様にコメントも独自の記述を編み出しちゃったりしててdocument作成ツールに食わせられないものも多数。これも自動化していないってことは仕様書への反映が怪しいことの証明なので評価を落とす。
    年寄り的解説:これは百害しかないので言い訳できないよ
  5. 必要以上に広いスコープで変数宣言する
    これもごく初期のC(ANSI以前)では「関数先頭での宣言」以外は許されなかったりしたが、ANSI(1989)以降はブロック先頭での宣言~どこでも宣言と言い訳にならなくなっている。
    年寄り的解説:「LSI C-86試食版ではそうだから(キリッ」
  6. ハンガリアン
    これはまーいろいろ紆余曲折あったんだろうけれど、変数名に型情報を含めることは結局何の解決にもならない。だって嘘混じってるんだものw。
    今時普通にIDE使ってリファクタリングもワンタッチなら型変更にも耐えられるんだろうが、こいつら秀丸でgrepしかしないから結局型変更しても変数名変えずってのが増殖する。
    年寄り的解説:Microsoftとかがハンガリアンマンセーとかいう記事を出すとそのままマンセーしちゃったり。それを考え無しで後継に押し付けたんだろね
  7. ヨーダ記法
    元々は、「==と=を間違えた時にコンパイルエラーとなるように」と誰かが編み出した書き方なんだけれど、もうずいぶん前からコンパイルはwarningを出せるようになっている。(LSI C-86試食版は知らん)
    年寄り的解説:「俺はこういう経緯を知っているのだぜ」アピール以外の何者でもない。しかも何の役にも立たない。
  8. 言語として用意されている機能を使わない
    これについては指摘する際にちょっと注意してもらいたい。
    最近の言語であっても(スクリプト系であれば特に)「使ってはいけない」理由があったり、「使わない」理由があるケースが多い。
    使わない=知らない であることも多いだろうがそうでない場合も結構あるので自身の実になるかもしれないのでまず聞こう。
    年寄り的解説:とりあえず経験だけはあるので、本人が忘れていても体が覚えてそうしていることもある。それが適当であるのかないのかまではすぐに思い出せないが一緒に考えようw
  9. goto
    そりゃー通常は使っちゃいけないNo.1ですよ。今も昔も。
    でもあえて使う場面がないわけではない。が、やっぱり何か変なことがほとんどなので特殊な組込み系以外では治すべき。
    年寄り的解説:「だからdo~while(1)でbreakしろと」って輩も出てくる。もうそういうやつは無視して全部ロジック組みなおすのをお勧め。こういうやつらは治らない


とりあえず年寄り(もちろん俺含め)は理由をつけたがるが、これは若い人が素直すぎるのも一因。なんで信じるんだよーと。
コードのコピペもそう。まぁ張ってエラーをちょっと直していくと出来上がるってのを全否定はしないが、そこで何か考えなくてよかったのか?気にならないのか?と気になってしまう。

とまー、たまにぐずぐず言っておかないと心が病むので無責任な愚痴を吐き出しっと。
スポンサーサイト



削除フラグ

データベースのテーブル設計をしていると、最近頓に目に付くのが削除フラグ
自称データベースに詳しい人間は事象を把握するのにデータベースを開く。
開発とかプログラミングを知らないのにSQLを多少それっぽく操作できるというだけでデータベースを開く。SELECT * で。
一つのテーブルで全てが目視できることを望む彼らは結合を嫌う。
そういう彼らを重宝する向きもあったのであろう現場では、障害の原因を彼らに尋ねるようになるが、データの状況だけで何がわかるはずもない。そこで彼らは履歴を求めるようになった。だがしかし彼らの武器はSELECT *のみ。
そこで編み出されたのが削除フラグ。DELETEの代わりに削除するときにONにするフィールドだ。

悲しいことに彼らは制約という概念を理解していないので、どのテーブルでもauto-incrementのフィールドのみのインデクスだし、複数フィールドでのUNIQUEな制約など全く念頭にない。まぁつまりお構いなしなわけで。
削除フラグをON/OFFにしているために、そのテーブルには一切のUNIQUE制約をつけることができなくなるが彼らには関係のない話なのだろう。

テーブル設計に削除フラグが存在している時点でその開発能力に疑問符がつきお里が知れる状況であることを理解していない。品質云々以前のお話。

まずデータベースの更新履歴が本当に必要な業務であるなら、データベースレベルでのトランザクションログをしっかり取るべきで、それ以上もそれ以下もない。やれという話なわけで。
業務系システムならば通常は更新する側のアプリケーションでしっかりログ記録しておけば済む話である。SELECT *で履歴を知ろうなどと考えるのがおかしい。
百歩譲って削除フラグを設けるとすれば、都度UNIQUEな値をセットする事にする(例えばNULLが有効、削除時には都度UUID的なユニークな値をセットする)とかにしないと、そもそもデータベースとして役に立たない。
まぁどちらにしても無駄としかいいようがないのだが。内容をチェックしたいのならチェックするツールを作れよ。SELECT *で全てがわかるって本当に思っているわけじゃあるまいに。

自称データベースに詳しい人は、業務アプリケーションの仕様を紐解かずにある程度の動作を理解できるようなテーブル設計に労力を割いて欲しい。制約がきちんと設定してあればそれだけで無駄な調査が減る。ありえないデータはそもそも登録できないのだから。NOT NULLなりDEFAULTなりUNIQUEなりでアプリケーション側の負担も相当減るし運用後の調査やリグレッションテスト項目だって激減するのだから。

これからIT系の世に出ようとする方々には是非とも
削除フラグ?馬鹿なのか?
と声に出してもらいたい。

Rustってやつ

とうとうRustに手を出さざるを得なく、というほどでもないのだが、知識として持っておかないといけない気がして。

Rust(プログラミング言語) - Wikipedia

本家本元はというと
Rust Programming Languageの模様。

さてこれで何をするかというと、何もする予定がないのだけれど、JavaやC#でさえInterfaceを理解していない輩が溢れる業務系の開発現場ではたしてRustのtraitは受け入れられるものなのだろうかという疑問を解決したい。
おりしもDevelopers Festa Sapporo 2020(2020/11/18)ではRustのいい話が聴けるとか聴けないとか。

Pythonにはまるで興味が湧かなかったんだが、果たしてRustはっ

元号

緊張した面持ちの小渕さんが掲げた「平成」のシーンが記憶に残る唯一の改元なわけだが、そろそろその平成も終わりに近づき。
次は何だとか、平成継続とかいろいろ話はあるのだが、プログラム的に元号が必要な場面は、公的な文書や銀行関係への文書とかに限定される。他にも使っているところはあるだろうが、今回を機に西暦表記へ変更するところも少なくないだろう。
まぁそれでも元号表記は残るわけで。

古くからのプログラムの中には独自のテーブルを作って元号を取得するものも少なくない。
和暦テーブルとして一般的なのは、その元号の開始日(西暦)と元号で足りる。
1989/01/08,平成
みたいな感じで、過去のものも必要なら並べて、対応する元号を探して表示、てなことをする。
これのメリットは「好きなようにできる」ことであり、OS/LibraryがUpdateできなくても定義に追加すれば取得できる。

とぉ言われていたりもするわけだが、OS/LibraryだってUpdateしなくても定義変えればいけちゃうので、自前テーブル作る意味は殆ど無い。全くないとは言わない。「年度計算」が残るわけだし。

で本題。
単純に和暦が欲しい場合、gccならばstrftimeに任せる。
strftimeではいろいろ特殊な書式があって、和暦の場合は%Eを使う。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <locale.h>

int main(void) {
time_t t = time(NULL);
char buf[128];
char *curlocale = setlocale(LC_TIME, NULL); //現在localeを取得
if (setlocale(LC_TIME, "ja_JP.utf8") != NULL) {
strftime(buf, sizeof buf, "%EC%Ey年%m月%d日", localtime(&t));
puts(buf)l;
setlocale(LC_TIME, curlocale); //localeを元に戻す
}

return 0;
}

これで簡単に和暦が取れちゃうわけなので、ごちゃごちゃやらずにstrftime使いましょう。
改元があっても、glibcがUpdateされればそのまま使えるけれど、業務用システムでUpdateなんてとんでもない、のような場合は、localedefで独自定義作っちゃえば良し。

Windowsの場合、今環境がなくて確認できないけれど、cliならばDateTimeの書式に%ggが使えたような。OleDateTimeでも。strftimeでももしかしたら使えるのかもしれない。
で、こいつは時刻の設定とか地域の設定とかで定義されるロケールを使用するはずなので、改元があってUpdateできない環境であってもレジストリいぢればいける。
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Nls/Calendars/Japanese/Era
あたりにそれっぽく入っているような。
それっぽく追加してやればそれっぽく動くんじゃないかな。

Linux Mintでgdbm

Linux Mintでgdbmを使ってみた。
gdbmはまぁ機能の無いISAMみたいなもんで、もしかしていつかどこかで使うこともあるのかもしれない、ということで。

でまず、
man dbm_open
とかやってみたら無い。
なので、
sudo apt-get install libgdbm-dev
とインストール。
manの中身をつらつら見てみると、なんかRetHat系のと違う。
とりあえずmanの説明通りにそれっぽく。
目的はdbm_open等のDBM系関数を使うことなので、manの仰せのとおり
#include <ndbm.h>
とするが見つからず。
ndbmを使うには、gdbm-ndbm.hを使う必要があるようだ。
そいでmanの仰せの通り、
-lgdbm -lgdbm-compat
をコンパイルオプションに付けてコンパイル。


起動時引数で、c:Create and Store、r:キー指定でFetch、f:順に取得、2:キー(int)をshortで取得してみた。
2:ではついでにdbm_errorも呼んでみた。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <gdbm-ndbm.h>

#define DbName "testdb"
void Create();
void Read();
void Fetch();
void Fetch2();

int main(int argc, char **argv)
{
if (argc < 2) { exit(EXIT_FAILURE); }
switch (argv[1][0]) {
case 'C':
case 'c':
Create();
break;
case 'R':
case 'r':
Read();
break;
case 'F':
case 'f':
Fetch();
break;
case '2':
Fetch2();
break;
default:
return 0;
}

return 0;
}

void Create()
{
DBM *dbf = dbm_open(DbName
, O_RDWR | O_CREAT
, S_IRUSR | S_IWUSR
);
if (dbf == NULL) { fprintf(stderr, "dbm_open error\n"); return; }
int i;
for (i = 0; i < 100; i++) {
datum key = {.dsize = sizeof(int), .dptr = (void *)&i };
int n = i * 3;
datum content = {.dsize = sizeof(int), .dptr = (void *)&n };
if (dbm_store(dbf, key, content, DBM_REPLACE) != 0) {
fprintf(stderr, "dbm_store error\n");
dbm_close(dbf);
return;
}
}
dbm_close(dbf);
}

void Read()
{
DBM *dbf = dbm_open(DbName, O_RDONLY, S_IRUSR);
if (dbf == NULL) { fprintf(stderr, "dbm_open error\n"); return; }
datum key = dbm_firstkey(dbf);
while (key.dptr != NULL) {
datum content = dbm_fetch(dbf, key);
if (content.dptr != NULL) {
printf("key=%d content=%d\n"
, *(int *)key.dptr
, *(int *)content.dptr);
}
key = dbm_nextkey(dbf, key);
}
dbm_close(dbf);
}

void Fetch()
{
DBM *dbf = dbm_open(DbName, O_RDONLY, S_IRUSR);
if (dbf == NULL) { fprintf(stderr, "dbm_open error\n"); return; }
int i;
for (i = 0; i < 100; i++) {
datum key = {.dsize = sizeof(int), .dptr = (void *)&i };
datum content = dbm_fetch(dbf, key);
if (content.dptr == NULL) {
fprintf(stderr, "dbm_fetch error\n");
dbm_close(dbf);
return;
}
printf("key=%d content=%d\n"
, *(int *)key.dptr
, *(int *)content.dptr);
}
dbm_close(dbf);
}

void Fetch2()
{
DBM *dbf = dbm_open(DbName, O_RDONLY, S_IRUSR);
if (dbf == NULL) { fprintf(stderr, "dbm_open error\n"); return; }
int i;
for (i = 0; i < 100; i++) {
short n = (short)i;
datum key = {.dsize = sizeof(short), .dptr = (void *)&n };
datum content = dbm_fetch(dbf, key);
if (content.dptr == NULL) {
fprintf(stderr, "dbm_fetch error[%d]\n", dbm_error(dbf));
dbm_close(dbf);
return;
}
printf("key=%d content=%d\n"
, *(short *)key.dptr
, *(int *)content.dptr);
}
dbm_close(dbf);
}


Create、Store、Fetch、順読みはいずれも成功。期待通り。
そしてkeyをshortで読みにいくのは当然不一致エラー。
dbm_errorもヘッダで(0)にdefineしてるだけなので当然0。
まぁいろいろと残念ながら期待通り。

mapのシリアライズには使えるかなぁ?、とまぁそんな感じ。
素直にgdbm_系関数使った方が汎用的かなとかは考えないでおく。
プロフィール

f_yamaki

Author:f_yamaki

アクセスカウンタ
最近の記事
最近のコメント
最近のトラックバック
月別アーカイブ
カテゴリー
ブロとも申請フォーム

この人とブロともになる

ブログ内検索
RSSフィード
リンク