Chromeとかで使われているグラフィクスライブラリSkiaのWindowsビルド
手順
git clone https://github.com/google/skia
masterブランチだとWindowsに優しいものが入っていないのでtagを使います。
git checkout -b dev refs/tags/chrome/m39_2138
上記タグのコミットだとリポジトリルートにmake.batが存在するので、こちらをVisual Studioの環境変数を適用した上で実行するとビルドできます。
環境変数の準備は一般的に面倒なので設定済み開発用コマンドプロンプトを利用すべきでしょう。
VSのインストールパスの下記バッチファイルを起動させたり、ウィンドウズアイコンのインストールしたアプリから呼ぶこともできます。
VisualStudio/Common7/Tools/VsDevCmd.bat
ただ1つだけ罠があって、いくつかのファイルがBOMなしutf-8で保存されているためビルドが失敗します。
私はそれらのファイルにBOMを追加することで対応しました。
最初はファイルエンコードを一致させることで解決しようと思ったのですが他のファイルにvimで:set fenc?としても
fileencoding=としか返してくれず諦めました。読み取れなかったらこうなるんですかね。
参考
この記事によりBOMなしutf-8が原因だと気付けました。ありがとうございます。
std::vectorのようなコピーコストの高いクラスに参照を使ってはいけないシチュエーション
概要
RVO(Return Value Optimization)の話です。特別に触れませんがNRVO(Named RVO)も最適化されるものとして話を進めています。
昔の効率の良いコード
void Initialize(std::vector<int>& buffer) { // bufferに色々な計算結果を詰める buffer.emplace_back(5); }
昔は新しいデータ列を作成する際、アロケートの際には上記のような関数が使われていました。
それ故に下記のような分かり難い記述が散らかることになります。
std::vector<int> buffer; // これくらいならまだ良いけど Initialize(buffer); // 生成に必要な引数が増えると何がなんだか Create(buffer, elementA, elementIndex, combineBuffer); // 使う Use(buffer);
しかし、これには仕方ない事情がありました。
std::vectorを返すのがスマートだと分かっていても、そんな大きなものを返すとコピーコストが高いことが予想されるからです。
今の効率の良いコード
// 引数によってbufferが生成されていることがはっきり分かる auto buffer = Create(elementA, elementIndex, combineBuffer); // 使う Use(buffer);
ここでRVOを知らない人はbufferの型がstd::vector
関数を見てみましょう。
std::vector<int> Create(...) { std::vector<int> result; // 色々な計算結果を詰める result.emplace_back(5); return result; }
確かにRVOが効かなかった昔のコンパイラならば最悪のコードです。膨大なバッファのコピーが無意味に走る。
しかし、今の私たちにはRVOがあります。
上記コードは以下のコードと同等と解釈されます。
std::vector<int> buffer; // 色々な計算結果を詰める buffer.emplace_back(5); // 使う Use(buffer);
つまり、バッファポインタのコピーコストや関数の呼び出しコストすらも無く同様の処理を記述できるようになったのです。
昔のコードよりも高速に処理される上に遥かに高い可読性を持つこの初期化方法は想像以上に多くの可能性を私たちに提供します。
まず、コピーコストを無視できるので何らかのクラスを生成している箇所を機械的に分離できるようになります。
長い関数は大抵途中でなんらかのデータ列を生成していることが多いので、その部分だけでも分離することで効率を落とさずに可読性を上げることができます。
もうすでにRVOを行わないコンパイラは少ないと思っています。
加えて、最近の規格ではRVOを保証するものも出て来ているので上記のような書き方をしない理由はないと思います。
3Dプログラマのための数学
概要
画像付きで詳細に解説しようとしたらモチベーションが足らなかったので下書きをブログで。
Qiitaとかだと数学記号使えるので恰好良く書ける反面書くの面倒。
内積
3Dプログラミングで一番出てくるやつ。
何故大人気かというと三角関数の中で一番高速で計算できるから。
内積は
float DotProduct(const float3& a, const float3& b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
のように足し算と掛け算のみで計算できるので速い。
ちなみに引き算は問題ないが割り算は他の演算と比べるとちょっと遅い。
それでもcos関数とかsqrt関数と比べるとマシ。
一般的な認識はそんな所だと思うけど、最近のGPUやCPUは進化しているのでnvidiaの出している情報を見ると、この関数は1命令でいける、これは4命令かかるとかが書いてあったりするので決まった環境での最適化はもうかなり特化した人じゃないと難しそうです。
まー、今はモバイルも強いので上記の一般常識に従う方向でも良いでしょう。
話がそれました。
内積は
DotProduct(a, b) = aベクトルの長さ * bベクトルの長さ * cos(aベクトルとbベクトルの作る角度);
とも表すことができるので、aベクトルとbベクトルが単位ベクトル(長さが1のベクトル)の場合aとbベクトルのcosが取得できます。
またaベクトルのbベクトル成分が欲しい場合はaベクトルとbの単位ベクトルの内積をとってやればaの長さ * cosとなり取得できます。
この操作は射影(Projection)とも呼ばれaベクトルからbベクトルに垂線を引いた時の長さに相当します。
3Dでこの情報が欲しい場合の例としては法線ベクトルや接線ベクトル成分を取得したい場合等があります。
長さを求めるのは大変
長さを求めるためにはsqrt関数を使わないといけません。これは他の演算と比べると重いので使いたくありません。
なので長さの2乗を使います。長さの2乗は
return x * x + y * y + z * z;
で取得できるので高速です。
長さの2乗を計算する関数が用意されていない環境ではDotProduct(a, a)のように内積で代用したりします。
ベクトルは長さをもっている
ベクトルaを考えるとき、aベクトルは
aベクトルの長さ * aベクトルの単位ベクトル
と考えることもできます。
ここから一切の特殊な演算を使わずにaベクトルのbベクトル成分ベクトルを求めることができます。
aベクトル * DotProduct(a, b) / DotProduct(a, a)
上記演算の式を整理すると
aの単位ベクトル * bの長さ * cos
となり、aとbの長さを求めていないのにaとbの長さが必要な演算ができてしまうのです。
単位ベクトルを求めるためにはaベクトル / aベクトルの長さを計算する必要がありますからね。
外積
外積は計算上は
return float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
これはやはり引き算と掛け算のみなので高速です。
しかし、演算結果にベクトルを含んでしまうので扱いにくいです。
意味としては
aの長さ * bの長さ * sin(aとbの作る角) * aとbが作る面の法線単位ベクトル
で表されます。 sinとcosは相反するほぼ同じ意味を持つので、扱いにくい外積よりも内積を使うというわけですね。
最後に
はい、「3Dプログラマのための数学」なんて言っておきながら足し算と引き算と掛け算しかしませんでした。
数学関数を高速動作させるやべーやつも研究されていますが、内積で解決できる問題は内積で解決するのが一番速いです。 github.com
C++コーディングガイドライン哲学1
概要
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md
の「P: Philosophy」の感想や補足です。
アイディアを直接コードで表現する
例としてStrong typeやfor文の代わりにアルゴリズムを使うことを提案しています。
また、constを一貫して使うことやキャストによる型システム破壊の防止について言及しています。
Strong type
プリミティブ型のような汎用性の高い型は間違いの元なので、間違った操作ができない型を使おうという発想です。
IndexA indexA(10); IndexB indexB(5); A a; B b; a[indexA]; // OK a[indexB]; // コンパイルエラー if(indexA == IndexA(10) && indexB == IndexB(5){} // OK if(indexA == 5 && indexB == 10){} // コンパイルエラー
Indexが全部int型だと必ずミスをします。自分は良くても後で保守する人は必ずミスをします。
スタンダードライブラリにある機能を再発明しない
これは簡単なようでいてかなり難しいです。これを正しく運用するためにはスタンダードライブラリの全機能を把握する必要があるからです。無理です。
なので、よくある処理を書く前にライブラリにないかな?といったんググるだけで良いと思います。
良く使いそうな例を少し挙げておきます。
一般的にあって欲しいfilterとmap(LinqだとWhereとSelect)もあるといえばあるけど・・・状態なので結局作らなければいけないことも多いです。
boostまで考慮に入れるともう少し便利になります。
std::remove_ifとcontainer::erase
条件を満たした要素の配列を取得できます。
参照配列を弄って欲しくない時はコピーするしかない?std::transform 指定した方法で変換された配列を取得します。
変換後結果を詰めた配列を返すものということは知っておいた方が良いでしょう。 Boost.Rangeをパイプライン記法のままコンテナに変換 - Qiita
for文でこの処理を書くとかなり蛇足感が出ます。
std::all_of 全ての要素が条件を満たすか確認します。
std::any_of どれか1つでも条件を満たす要素があるか確認します。
algorithmぐらい頻繁に使うがnumericにあるので存在を忘れやすい
- std::accumulate 総和を求める
constを一貫して使う
constは伝搬されるものなので最初から心がけていないと「あれ?この関数にconst変数渡せないぞ」となってしまい、constを使わない文化が蔓延してしまうので早めに取り組んだ方が良いでしょう。
constに気をつける箇所はたくさんあります。
class Input { public: // メンバ関数のconst Arguments GetArguments() const { return arguments; } private: ... // メンバ変数のconst // コンストラクタで初期化しないとエラーとなるので使わない人も多いですが、const性を持つのなら定義すべきです const int version; }; // 引数のconst void Function(const Input& input) { // ローカル変数のconst // 蛇足っぽく見えるので嫌う人も多いですがconstを常に使う方が哲学に沿います const auto& arguments = input.GetArguments(); }
キャストした際にはそれがキャストされた値だと明示する
おそらくstd::anyやstd::variantについて言及しているのだと思います。
別の型として汎用管理しても良いけど元の型を忘れちゃダメ。
ユーザ定義リテラル
例として下記のようなコードが提示されています。
change_speed(23m / 10s); // meters per second
何の説明もなく出現していますが、これは疑似コードではなくユーザ定義リテラルです。
ユーザー定義リテラル - cpprefjp C++日本語リファレンス
なにかと評判の良くないユーザ定義リテラルですが、ガイドラインで利用されていることを考えると使っていった方が良いのでしょうね。
ISO Standard C++を使う
理由は移植性だと思うのですが、このガイドラインはISOのために書かれているからISO Standard C++を使おうってなってます。
移植性では納得しない人が多かったのでしょうか。
まー、守ることは環境依存な拡張機能は使わないようにしようってことです。
意図を表現しよう
例えば配列の各要素について処理したい場合はwhileではなくforを使おうということです。
上記ぐらいは多くのプログラマーが実施できているとは思いますが、Read Writeの意図を明示できている人は思っているよりも少ないです。
// 参照のみすることを明示する for (const auto& x : v) {} // 書き換えを行うことを明示する for (auto& x : v) {}
また、プリミティブ型は曖昧になりがちなので意味を表現した型を使うことを勧めています。
// 各パラメータが何を指しているのか分かり辛い draw_line(int, int, int, int); // 分かりやすい draw_line(Point, Point);
最後により良い互換機能が提供されているものについてはそちらを使いましょう。
- 単純なforはrage baseのforに
- ポインタとその長さの組み合わせによる範囲指定はspan
に spanとはGuideline Support Library(gsl)に定義されるクラス。MSによる実装が下記
GSL/span at master · Microsoft/GSL · GitHub - あまりに長いスコープ変数 ネストが深くなったりスコープが長くなり過ぎる場合、意味単位に分割してクラス化や関数化を行い、スコープはせめて1画面以内にするのが望ましいと私は思います。
- 生のnewやdeleteはスマートポインタやmake_shared、make_uniqueに
- 関数の引数にプリミティブ型が多い場合は意味を表現した型を使うように
理想的には、プログラムは静的型安全であるべき
クラッシュやセキュリティの脅威となる既存の方法への代替案として下記を提案しています。
- union(共用体)の代わりにstd::variant
- キャストの代わりにtemplate
- 部分配列の利用にポインタやインデックスを使うのではなくgsl::span
- 範囲外アクセスエラーもgsl::spanなら検出できる
- キャストするとしてもgsl::narrowやgsl::narrow_castを使おう narrowはnarrow<IndexA, IndexB>でIndexAをIndexBにstatic_castした値が入っているよと明示するクラス
実行時チェックよりもコンパイル時チェックの方が良い
間違ったコードはコンパイル時にエラーを出し、決して実行時までエラーを遅延させてはいけない。
また、static_assert等で検証した事項を実行時に再検証してはいけない。
コンパイル時にチェックできないものは、実行時にチェックできなければならない
上記タイトルだと幅広そうですけど、ようするにレンジアクセスにポインタとインデックスは使わずにgsl::span使おうぜって内容です。
実行時エラーは早めに捕捉しよう
nullポインターに代表される多重チェックをしないためにどうするべきか。
nullチェックが必要なポインタの代わりに参照を使うように、境界値チェックが必要な配列とレンジを指定する代わりにspanを使うようにチェックが終わって安心して使えることを明示する型を利用するようにしましょう。ということだと思います。次の項目で出てくるRAII的な考え方をエラーチェックにも適用しようということでしょう。
おわりに
これで哲学の項目の半分くらいです。続きは気が向いたら書きます。
好きなC++ランキング
好きなだけで使えるとは言っていない所がポイントです。
コンパイラだけでなく、周辺環境も含めて考慮しているのでC++としています。
1位 wandbox + clang/gcc
個人的に新機能を試す時が一番幸福度が高いのでこの組み合わせが好きです。
環境構築が必要なしに新機能を試せるのは本当にありがたいです。好き。
立ち上がる速度、実行までの速度、速度でいえばこれに勝てる構成はないです。好き。
ただ、ファイル数が多かったり、自分の好きなエディタと連携しようと思うと面倒だったりは良くないです。Webサービスである以上は仕方ないのですが。
また、私が愛用しているchromeプラグインVimiumとも相性が悪いです。対象ページでプラグインをオフにするとページ切り替えもたつくし、オンにしていると不意にページを閉じてしまったりで。
後、boostがいまいち使いにくい印象を受けます。コンパイルが必要なライブラリ周りがどうやったら試せるのか分からなかったのかな?
以前、boost/qvmの機能を試そうとした時に上手に解決できませんでした。
2位 Cloud9 + clang/gcc
今はカード登録が必要になった?みたいですが、私は昔っから使っているお陰か登録していないので気軽です。
ようするただのVM環境なのでLinuxでいーじゃん的な意見はもっともなのですが、新機能や新しいことを試す時に一番重視したいのは環境の再構築しやすさなので私的には外せないポイントだったりします。クリーンなLinuxマシーンが1分かからずに作れて、何個でも作って良いとかうおおーってなります。好き。
こちらもテキストエディタ関係でwandboxと同じ悩みを抱えていますが、純粋なvimを使えるのでマシです。ただ、vimで日本語を扱うと表示がバグります。日本語を扱う場合は素直に備え付けのエディタを使わなければなりません。うーむ。
後、私は職業柄GUI関係のコードの実験を行いたいことが多いのですが、そちらの実験ができません。これは仕方ない。
また、無料でつかえるマシンのメモリや処理速度が貧弱なので一部ビルドが辛いです。Railsのinstallにそのままだとコケたりします。無料だとしばらくアクセスしなかったVMにアクセスするのに時間がかかるのも面倒です。しかし、ここらへんはお金で解決できる問題なので仕方ないです。私も使用頻度がもう少し高ければ払っていきたいです。
3位 JetBrains + clang/gcc/VC
この組み合わせは私がヘビーに試せていないだけで1位になるポテンシャルを秘めていると思っています。
私がしっかりと試したのはAndroidStudio + gccだけなのでその知見で書きます。CLionは触るだけしかしていないので、最初のセッティングでvimのキーバインドを選択できたのが感動した程度しか言えないです。
上記2つと比べるとマシンに入れる必要があるので起動時間、アップデート時間、環境汚染等は避けられません。しかし、それを補って余りあるメリットを提供してくれます。
素晴らしいと思うのは、CMake前提のプロジェクト構成をデフォルトで吐き出してくれることです。これならCMakeやMakeの知識がない人でもC++の簡単なプロジェクト管理ができます。しかもツール、環境間の相互運用性も高い。グッジョブですね。
また、テキストエディタとvimのキーバインドの相性も良く特に不満なく使えます。上記2つとは比べものにならない自動補間、自動問題解決を行ってくれます。
最大の問題点。AndroidStudioでのC++はAndroid向けアーキテクチャに対してコンパイルされるので汎用性が低い。CLionは期間限定で無料試用はできるものの有料。
個人的には開発環境が有料というのはちょっと無いので、せめて個人利用の範囲では無料にしてくれるまで押すつもりはないです。個人でがっつり使ってないものを企業で推進するのは難しいと思いますよっと。
4位 VM + clang/gcc
最近のマシンは高性能になってきているのでVMを常時起動してても重さは感じないので素晴らしいですね。
この構成もLinuxで良いじゃねーか案件なんですが、私が常用しているPCはWindowsだし、上記で触れた環境の壊しやすさを考慮するとこの選択になります。
VMには昔はVMwareを使っていたんですが、最近ではVirtualBoxとVagrantで運用していました。
Web環境の際に不満だったGUIの実験ができ、環境的にもただのLinuxなので完璧かのようにみえます。
しかし、やはりVM関係の特定の操作には時間がかかる、複数のVMを管理しているとストレージを圧迫するし簡単に管理できる何かが必要という感じになりました。
それにVM上でIDEまで入れるのなら流石にLinux機で良いと思うので立ち位置が微妙な構成に感じます。
また、これが私にとって一番致命的だった理由なのですが、VMの操作を色々行った後はVirtual Network Bridgeが生成され、それがネトゲのPingに深刻な影響を与えることが時々発生したためです。開発のために日常を犠牲にするなんてあってはならないことです。
5位 VisualStudio + VC
はい、仕事ではもっぱらこれです。
vimキーバインドもプラグインによりほぼ不満なく動作します。その上優秀な補完、定義へのジャンプ、検索、デバッグと至れり尽くせりです。
昔は絶対に使いたくなかったVCですが、VisualStudio2015辺りに付随するVCからしっかりと新しいC++の機能に対応してきてかなり良くなってきています。
また、NuGetによるライブラリ連携も簡単にできるようになっており、テストやboost、OpenCV等を簡単に導入できるもの魅力的です。
しかし、他の構成では扱わない規模のプロジェクトを扱っているので公正な判断とは言いづらいですが、重い、固まるのが最大のデメリットです。
補完や定義へのジャンプを要求したら数十秒返事が返ってこないのが頻発します。やってらんねぇ。
また、ソリューションファイルが扱い辛いです。特定のパスを要求しようと思ったらソリューションファイルに直書きするしかないとかなるんなら、初めから設定ファイルを書けるプロジェクトを生成して欲しい。CMake等でソリューションファイルを生成した方が分かりやすく想定した動作をします。
6位 MinGW (or MinGW-W64), Cygwin + gcc/clang
昔はお世話になったこの構成ですが、VMも走らせることができる今となっては選択しなくても良いのかなと思います。
IDEが内部でこれらを処理してくれるので、自分で触る必要はないと思います。
ただ、MinGWのコマンドは便利なのでC++のコンパイル環境以外としてなら使うべきだと思います。
私はgit bash(gitに最適化されたMinGW環境)でそれらを代用しています。
おまけ vcpkg
GitHub - Microsoft/vcpkg: VC++ Packaging Tool
NuGetの代わりになるかと思いきや管理が大変過ぎて使えない感じになってしまっている惜しいプロジェクト。
各cプロジェクトを依存関係を考慮してビルド、リンクできるようにするパッケージマネージャ。
それだけ聞くと最高な感じだが、実状として各プロジェクトの依存関係やビルドファイルを全部人力で構築しているので対応が遅れるのとバグが取り切れてない。
誰でも編集できるようにすると質の低いビルドファイルが作られるという理由で中央集権化した(NuGetが低質なビルドファイルにより壊れているパッケージが多数あることを受けてかな?)結果役に立たないものになってしまった感じ。
やはり、やるべきは良質なテンプレートの提供とデベロッパーの啓蒙なのだろう。
おまけ Unreal C++
Unreal Engineをビルドする際に使用される特殊加工されたビルド環境です。
Reflectionが使えないC++に無理矢理ではあるもののスマートにReflectionをもたらした事は素晴らしいと思います。関数名やクラス名をそのままエディタ上で扱えることは苦労以上のメリットがあると考えます。
また、モジュールの概念が素晴らしく、これにより明確なスコープの管理と公開範囲の管理、ホットリロード等が実現されます。
このシステムについての不満としてはプリフィックスの強制と使っているコーディング規約、膨大なファイル量による重さがあります。
最後に
では、みなさん。良きC++を。
自分で作ったクラスをrange based forに対応させよう
Range based forは素敵
for(auto value: vector){ }
良いですね、スマートです。
しかし、これstd::vector等のスタンダードライブラリに入っているクラスしか使えないと思ってはいないでしょうか?
私が体験した職場で自身のクラスをrange based forに対応させているパターンを見たことがありません。
案外簡単にできるので是非やってみてください。
最低限必要なこと
struct Iterator{ auto operator!=(Iterator){ return false; } auto operator++(){ } auto operator*(){ return nullptr; } }; struct Foo{ auto begin(){ return Iterator(); } auto end(){ return Iterator(); } }; int main() { Foo foo; for(auto item: foo){ } }
はい、これだけです。
Iteratorに既存のものを利用する場合はなんとbeginとendの関数を定義するだけでrange based forに対応できてしまうのです。
ただ、注意点もあります。
Iterator != Iteratorでループの終了条件を判定するので同じコンテナのIteratorを返すようにしないといけません。
もっと自由度を高くしたい場合は専用のイテレータを作った方が良いでしょう。
後、const版のbeginとendを作っておいた方が後々幸せになれます。
カスタムイテレータ
イテレータの方もそこまで難しいことはなく、range based forの判定に必要な関数を定義するだけです。
operator!=で現在のイテレータとend()のイテレータを比較します。現在のイテレータとend()のイテレータが一致した場合にループ処理を終了させます。
operator++で現在のイテレータを次のイテレータに変換します。
operator*でイテレータからforで使う値を取り出します。
auto item = *iterator;
と同じですね。
展開されるコード
上記クラスがどう処理されるのかも記述しておきます。
for (auto __begin = foo.begin(), __end = foo.end(); __begin != __end; ++__begin ) { auto item = *__begin; // range based forの中身 }
begin(), end(), operator!=, operator++, operator*が何故必要なのか一目でわかりますね。
参考
綺麗にコードを書く技術
概要
難しい処理をこなす処理を簡単に描けるプログラマではそのコードは汚いものです。
思うに綺麗にコードを書くことは技術のレベルとは関係ないので執着されないのでしょう。
しかし、私は綺麗にコードを書くことは無駄には思えません。
技術が技術と呼ばれる理由に人に共有し洗練していくことが可能という部分があると思います。
私の考える綺麗にコードを書く方法を共有することで技術として残せたらと思います。
リネーム程度のリファクタリングは挨拶かのようにする
汚いコードを書く人の特徴として、一度作成し終わった関数やクラスには追加しかしないというものがあります。
作成段階では引数、名前、メンバが正確じゃないのは当たり前です。
新しい処理を追加する度に与えられたコンテキストが変化していないか目を光らせて、変化していた場合はすぐさま変更しましょう。
スコープを常に最少にする
スコープ、変数のライフタイムを常に自身の考えられる中での最少にすると自然と綺麗なコードになります。
スコープが最少になることを意識してコードを書くと自然に仕切りが必要になり、それが関数として分離できるようになります。
大部分で使わないメンバは現在のクラスに相応しくないのではないかと考える
多数のメンバと多数の関数を眺めてみると良く見つかるのですが、特定の関数グループでしか利用しない変数が存在することがあります。
クラスを分割するチャンスです。
クラスをまたがるコンテキストに注意を払う
2つのクラスの関係を見て、あまりに密に入出力を交換している場合は2つのクラスのためのラッパーを書いた方が良いかもしれません。
継承は使わない
特にC++の話ですが、継承は使わない方が良いです。
継承を使ってコードを綺麗に書けている人を今まで見たことがありません。
多くの場合、仮想関数やらポリモーフィズムやらオーバライドしているしていない、基盤クラスの肥大化によりスパゲッティ化します。
自分は良くても後から来た人は大体読みにくいと思ってます。
Mix Inなら有効な手法なので、こちらを意識できる人は使っても良いと思います。
そういう人はダックタイピングも分かるでしょうし、節度を守れそうです。
cppファイルでのクラス宣言はしない
これはC++の話です。
cppファイルで宣言されたクラスは他のファイルでは使えないので、有用なクラスを作ってしまうと悲惨です。
特にリファクタリングが嫌いで既存のコードへの修正を嫌う人が保守した場合地獄が待っています。
その有用なクラスしか取得できない情報を取得するためのコードが山のように追加されてあらゆるコンテキスがそのファイルに依存するようになります。
そうなると手練れでもそのファイルを依存の海から救い出すのは難しいでしょう。
全てに対して入出力を意識する
関数が入力を受け取って出力を生成するように、全てのプログラミング上の事象の入出力を常に意識すべきです。
生成したいものがあったとしたら、その生成に最低限必要なものだけを与えて生成したいものを生成することに集中すべきです。
このあたりが混ざったコードは簡単にスパゲッティ化します。意識しましょう。
また、具体的なインスタンス等が無かったとしても、何らかの副作用が発生する場合はそれも出力として意識すべきです。
ファイル、関数を巨大にしない
これは当たり前のことなんですが、何故か守れない人が多いです。
スコープ、依存を最少にすることを意識していたら、そのまま全部小さくできるはずです。
変数名やクラス名にはこだわる
適当な名前を付けるくらいなら当然説明的で変な省略語のない名前が良いです。
しかし、変数名は生ものです。コンテキストが変われば名前も変わってきます。
そういう時は素直にエイリアスを貼って別名を付けましょう。
また、スコープに合わせて変数名の長さを調節することも大事です。
長いスコープで存在するものには長い名前を、短いスコープで存在するものには短い名前を与えましょう。
許容されるのなら既存の汚いコードには従わない
無意味なプリフィックスやネームスペースを使っていなかったり、謎のフォルダ分けがあったりします。
そんな場合、コード管理者に問い合わせてそのルールに従わなくても良いか聞くと案外気にしていなかったりします。
まぁ、そんなコードになってしまっている時点で管理できていないのは当たり前なのですが。
なので、容赦なく無視して汚いコード文化を綺麗なコードで上書きしていきましょう。
大丈夫、綺麗なコードを書こうとしている貴方の方が絶対に良いコード文化を築けるはずです。
また、自身に権限があるのなら綺麗に書くことの助けとなるようなコーディング規約を策定すべきでしょう。
私のオススメは
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md
に従うことですが、膨大過ぎるので少しづつ増やしていくのが現実的かと思います。