場合によっては、既存のコードを拡張または改善しなければならない状況に遭遇することがあります。古いコードは非常にリーンですが、拡張も難しく、読むのに時間がかかります。
それを最新のコードに置き換えるのは良い考えですか?
少し前に、無駄のないアプローチが好きでしたが、今では、より高度な抽象化、より優れたインターフェース、より読みやすく拡張可能なコードを優先するために、多くの最適化を犠牲にする方が良いようです。
コンパイラーも改善されているようで、struct abc = {}
のようなものは暗黙のうちにmemset
sに変わり、shared_ptr
sはrawポインターtwiddlingと同じコードを生成し、テンプレートは非常に機能します。彼らは非常に無駄のないコードを生成するので良いです。
しかし、それでも、スタックベースの配列といくつかの不明瞭なロジックを持つ古いC関数が見られることがあります。通常、これらはクリティカルパス上にありません。
いずれかの方法でコードの小さな部分に触れる必要がある場合は、そのようなコードを変更することをお勧めしますか?
どこ?
Googleスケールのウェブサイトのホームページでは許可されません。物事を可能な限り迅速にしてください。
1年間に1人が使用するアプリケーションの一部では、コードを読みやすくするためにパフォーマンスを犠牲にすることは完全に許容されます。
一般に、作業しているコードの一部の非機能要件は何ですか?アクションが900ミリ秒未満で実行する必要がある場合。特定のコンテキスト(マシン、負荷など)では80%の時間、実際には200ミリ秒未満で実行されます。 100%の確率で、パフォーマンスにわずかに影響する可能性がある場合でも、コードを読みやすくします。一方、同じアクションが10秒未満で実行されなかった場合は、パフォーマンス(またはそもそも要件)の何が問題であるかを確認する必要があります。
また、読みやすさの向上によりパフォーマンスがどのように低下するか多くの場合、開発者は動作を時期尚早の最適化に近づけています。コードは、同じアクションを実行するさらに数マイクロ秒を費やします。
通常、noです。
コードを変更すると、システムの他の場所で予期しないノックオンの問題が発生する可能性があります(堅実なユニットテストとスモークテストを実施していない場合、プロジェクトのかなり後の時点まで気付かれないことがあります)。私は通常、「壊れていなければ、直さない」という考え方で行きます。
このルールの例外は、このコードに触れる新しい機能を実装する場合です。その時点でそれが意味をなさず、実際にリファクタリングを行う必要がある場合は、リファクタリング時間(およびノックオンの問題に対処するための十分なテストとバッファ)がすべて見積もりに含まれている限り、それを続行します。
もちろん、profile、profile、profile(特にクリティカルパスエリアの場合)。
あなたは本当にあなたのリファクタリング/拡張バージョンを必要とするのでしょうか、それとも使いますか?
本当に最適化する必要がありますか?
ここで注意すべき点があり、実際に測定可能な利益と、個人的な好みと、コードに触れてはならない潜在的な悪い習慣との間の限界を特定する必要があります。
より具体的には、これを知ってください:
これはアンチパターンであり、組み込みの問題が付属しています。
KISSの原則 をリファレンスとして言及する人もいますが、ここでは直観に反しています。最適化された方法は単純な方法ですか、それともクリーンなアーキテクチャの方法ですか?以下の残りで説明するように、答えは必ずしも絶対的なものではありません。
YAGNIの原則 は他の問題と完全に直交しているわけではありませんが、自分自身に質問するのに役立ちます。
より複雑なアーキテクチャは、保守性が向上しているように見えるだけでなく、本当にメリットがありますか?
これを大きなポスターに書いて、画面の横、仕事中のキッチンエリア、または開発会議室に掛けます。もちろん、繰り返す価値のある他のマントラはたくさんありますが、「メンテナンス作業」をしようとし、それを「改善する」衝動を感じるときは常に、この特定のマントラが重要です。
コードを読んで理解しようとすると、無意識のうちにコードを「改善」したり、コードに触れたりしたくなるのは自然なことです。それは私たちが意見を述べ、内部をより深く理解しようとすることを意味するので、それは良いことですが、それは私たちのスキルレベル、私たちの知識にも束縛されます(どのようにして良いか悪いかをどうやって決めるのですか? ...)、そして私たちがソフトウェアについて知っていると私たちが思うことについて行うすべての仮定...:
このすべてが言った、なぜそれが最初に「最適化」されたのですか?彼らは 時期尚早の最適化 がすべての悪の根であり、文書化されておらず、最適化されているように見えるコードを目にした場合、通常、おそらく 最適化のルール に従っていないと想定できます最適化の努力を心から必要としておらず、それは単に通常の開発者のハブリスが始まっただけであることに変わりはありません。
その場合、どの制限内で許容可能になりますか?それが必要な場合、この制限が存在し、物事を改善する余地を与えます。
また、目に見えない特性にも注意してください。おそらく、このコードの「拡張可能」バージョンは、実行時に多くのメモリを消費し、実行可能ファイルの静的メモリフットプリントがさらに大きくなる可能性があります。光沢のあるOO機能には、これらのような直感的でないコストが伴います。これらの機能は、プログラムおよびプログラムが実行されるはずの環境に影響する場合があります。
Googleの今の人々は、すべてデータについてです!データでバックアップできる場合は、必要です。
これはそれほど古い話ではないので、開発に費やされた$ 1ごとに、少なくとも$ 1がテストに続き、少なくとも$ 1のサポート(ただし、実際にはそれよりもはるかに多い)。
変化は多くのことに影響します:
したがって、ここで測定する必要があるのは、ハードウェアリソースの消費量(実行速度またはメモリフットプリント)ではなく、でもあります。チームリソースの消費。どちらも、目標を定義するために予測され、測定され、説明され、開発に基づいて適応される必要があります。
そしてあなたのマネージャーにとって、それはそれを現在の開発計画に適合させることを意味しますので、それについてコミュニケーションを取り、猛烈なカウボーイ/サブマリン/ブラックオプスのコーディングに入らないでください。
私を誤解しないでください。一般に、私はあなたが提案する理由を支持したいと思います。私はそれを頻繁に主張します。ただし、長期的なコストに注意する必要があります。
完璧な世界では、それは適切なソリューションです。
実際には:
あなたはそれを悪化させるかもしれません
あなたはそれを見るためにより多くの眼球を必要とし、そしてそれを複雑にするほど、あなたはより多くの眼球を必要とします。
あなたは未来を予測することはできません
絶対に必要かどうか、必要な「拡張機能」が以前の形式で実装するのが簡単かつ迅速であったかどうか、そしてそれ自体が超最適化される必要があるかどうかさえも、確実に知ることはできません。 。
それは、経営者の観点からは、直接的な利益がないために莫大なコストを意味します。
あなたはここでそれはかなり小さな変更であり、いくつかの特定の問題を念頭に置いていると述べました。この場合は通常は問題ないと思いますが、ほとんどの場合、小さな変更、ほぼ外科的なストライクの編集の個人的なストーリーもあり、最終的にはメンテナンスの悪夢になり、Joe Programmerが1つも見ていなかったために締め切りに間に合わなかったり、爆発したりしましたコードの背後にある理由の中で、本来あるべきではない何かに触れました。
そのような決定を処理するプロセスがある場合、個人的なエッジをそれらから取り除きます。
しかし、もちろん、テストコードとメトリックは、実際のコードに対して回避しようとしているのと同じ問題に苦しむ可能性があります。正しいことをテストし、将来的に正しいことを行い、正しいことを測定しますか?物事?
それでも、一般的には、(特定の限界まで)テストして測定するほど、収集するデータが多くなり、安全になります。悪いアナロジータイム:それを運転のように考えてください(または一般的に人生):車があなたに故障した場合、または誰かが今日自分の車で車に乗り込んで自殺することを決めた場合、あなたは世界で最高のドライバーになることができますスキルだけでは不十分かもしれません。あなたを襲う可能性のある環境問題は両方あり、人為的ミスも重要です。
そして、私は最後の部分がここで鍵となると思います:コードレビューをしてください。あなたがそれらをソロにした場合、あなたはあなたの改善の価値を知りません。コードレビューは私たちの「廊下テスト」です。バグを検出するため、およびオーバーエンジニアリングやその他のアンチパターンを検出するため、そしてコードがあなたのコードと一致していることを確認するために、 レイナスの法則のレイモンドバージョン に従ってくださいチームの能力。 「最良の」コードを他に誰もいないと理解できず維持できても意味がありません。それは、暗号の最適化と6層の深いアーキテクチャ設計の両方に当てはまります。
最後に、次のことを覚えておいてください。
そもそもデバッグがプログラムを書くより2倍難しいことは誰もが知っています。それで、あなたがそれを書くときにあなたができる限り賢いなら、どうやってそれをデバッグしますか? - ブライアンカーニハン
一般的には、最初に読みやすさを重視し、後でパフォーマンスを重視する必要があります。ほとんどの場合、これらのパフォーマンスの最適化は無視できますが、メンテナンスコストが莫大になる可能性があります。
あなたが指摘したように、それらのほとんどはとにかくコンパイラによって最適化されるため、確かにすべての「小さな」ものは明快さを優先して変更する必要があります。
より大きな最適化については、妥当なパフォーマンスを達成するために最適化が実際に重要である可能性があります(これは驚くほど頻繁ではありませんが)。変更を加えてから、変更の前後のコードをプロファイルします。新しいコードに重大なパフォーマンスの問題がある場合は、常に最適化されたバージョンにロールバックできます。そうでない場合は、よりクリーンなコードバージョンをそのまま使用できます。
一度にコードの一部のみを変更し、リファクタリングの各ラウンド後にパフォーマンスにどのように影響するかを確認します。
これは、コードが最適化された理由と、コードを変更した場合の影響、およびコードが全体的なパフォーマンスに与える影響によって異なります。また、テストの変更を読み込むための適切な方法があるかどうかにも依存します。
この変更は、プロダクションで見られるものと同様の負荷の前後で、好ましくは負荷の下で行わないでください。つまり、開発者のマシンでデータの小さなサブセットを使用したり、1人のユーザーだけがシステムを使用しているときにテストしたりしないことを意味します。
最適化が最近の場合は、開発者に相談して、問題が何であったか、最適化前のアプリケーションの速度を正確に知ることができます。これは、最適化を実行する価値があるかどうか、および最適化が必要な条件について多くのことを知ることができます(たとえば、変更をテストしている場合、1年をカバーするレポートが9月または10月まで遅くなることはありませんでした) 2月には、速度低下はまだ明らかではなく、テストは無効である可能性があります)。
最適化がかなり古い場合は、新しいメソッドの方が高速で読みやすいかもしれません。
結局、これはあなたの上司の質問です。最適化されたものをリファクタリングし、変更が最終結果に影響を及ぼさず、以前の方法と同等または少なくとも許容範囲内で実行できることを確認するには、時間がかかります。彼は、コーディング時間を数分節約するためにリスクの高いタスクを実行する代わりに、他の領域で時間を費やすことを望んでいる場合があります。または、コードが理解しづらく、頻繁な介入が必要であり、より優れた方法が現在利用可能であることに同意するかもしれません。
プロファイリングは、最適化が不要である(クリティカルセクションにない)か、実行時間が長くなっている(時期尚早な最適化の結果として)ことを示している場合は、より簡単な読み取り可能なコードに置き換えてください。維持する
また、コードが適切なテストで同じように動作することを確認してください
ビジネスの観点から考えてみてください。変更の費用はいくらですか?コードをより簡単に拡張または保守できるようにすることで、変更を加えるのにどれだけの時間が必要で、長期的にどれだけ節約できるでしょうか。次に、その時間に値札を付け、それをパフォーマンスの低下によって失われたお金と比較します。おそらく、失われたパフォーマンスを補うためにサーバーを追加またはアップグレードする必要があります。製品が要件を満たしていないため、販売できなくなっている可能性があります。たぶん損失はありません。おそらく、この変更により堅牢性が向上し、他の場所で時間を節約できます。今あなたの決定をします。
余談ですが、場合によっては、フラグメントの両方のバージョンを保持できる可能性があります。ランダムな入力値を生成するテストを作成し、他のバージョンで結果を確認できます。 「巧妙な」ソリューションを使用して、完全に理解可能で明らかに正しいソリューションの結果をチェックし、それによって新しいソリューションが古いソリューションと同等であることをある程度保証します(証明はありません)。または、逆に行って、トリッキーなコードの結果を詳細なコードで確認し、ハッキングの背後にある意図を明確に文書化します。
答えは、一般性を失うことなく、はいです。コードが読みにくい場合は、常に最新のコードを追加し、ほとんどの場合は不良コードを削除してください。私は次のプロセスを使用します:
<function>_clean()
のような名前を付けます。次に、不正なコードとコードを「競合」させます。コードが優れている場合は、古いコードを削除してください。QED。
正当な理由なしに、作業用の製品コードを変更しないでください。あなたがcannotそのリファクタリングなしであなたの仕事をしない限り、「リファクタリング」は十分な理由ではありません。難しいコード自体のバグを修正する場合でも、時間をかけて理解し、最小限の変更を行う必要があります。コードが理解しにくいと、コードを完全に理解することができなくなり、変更を加えると予測できない副作用、つまりバグが発生します。変更が大きいほど、トラブルを引き起こす可能性が高くなります。
これには例外があります。理解できないコードにユニットテストの完全なセットがある場合、リファクタリングできます。完全な単体テストで理解できないコードを目にしたり聞いたりしたことがないので、最初に単体テストを記述し、それらの単体テストが実際にコードが何をすべきかを表すという必要な人々の同意を得て、コードを変更します。 。私はそれを1〜2回実行しました。それは首の痛みであり、非常に高価ですが、最終的には良い結果を生み出します。
理解しにくい方法で比較的簡単なことを行う短いコードの場合、拡張コメントや未使用の代替実装で「迅速な理解」をシフトします。
#ifdef READABLE_ALT_IMPLEMENTATION
double x=0;
for(double n: summands)
x += n;
return x;
#else
auto subsum = [&](int lb, int rb){
double x=0;
while(lb<rb)
x += summands[lb++];
return x;
};
double x_fin=0;
for(double nsm: par_eval( subsum
, partitions(n_threads, 0, summands.size()) ) )
x_fin += nsm;
return x_fin;
#endif
私が死ぬ前に世界に1つのこと(ソフトウェアについて)を教えることができれば、「パフォーマンス対X」は誤ったジレンマであると教えます。
リファクタリングは通常、読みやすさと信頼性の恩恵として知られていますが、最適化を簡単にサポートすることもできます。パフォーマンスの改善を一連のリファクタリングとして扱う場合、アプリケーションを高速化しながら、キャンプサイトルールを守ることができます。実際には、少なくとも私の意見では、そうすることは倫理的にあなたの責任です。
たとえば、この質問の作成者は、クレイジーなコードに遭遇しました。この人が私のコードを読んでいたら、クレイジーな部分が3〜4行あることに気付くでしょう。それ自体がメソッド内にあり、メソッド名と説明はメソッドが何をしているのかを示します。メソッドには、疑わしい外観にもかかわらず、クレイジーなコードが正しい答えを得る方法を説明する2〜6行のインラインコメントが含まれます。
このように区分けすると、このメソッドの実装を自由に入れ替えることができます。確かに、それはおそらく私が最初にクレイジーなバージョンを書いた方法です。試すか、少なくとも代替案について質問することを歓迎します。ほとんどの場合、素朴な実装は著しく悪い(通常、私は2〜10倍の改善のみを行う)ことがわかりますが、コンパイラとライブラリは常に変更されており、誰が今日入手できなかったのかを知っています。関数が書かれた?
触れることはおそらく良い考えではありません。コードがパフォーマンス上の理由でそのように記述されている場合、それを変更すると、以前に解決されたパフォーマンスの問題が発生する可能性があります。
doを読みやすく拡張可能なものに変更する場合:変更を行う前に、古いコードをheavyでベンチマークしてください。この奇妙に見えるコードが修正するはずのパフォーマンスの問題を説明する古いドキュメントまたはトラブルチケットを見つけることができれば、さらに良いでしょう。次に、変更を加えた後、パフォーマンステストを再度実行します。それがそれほど変わらない場合、またはまだ許容可能なパラメータの範囲内であれば、おそらく問題ありません。
システムの他の部分が変更されたときに、パフォーマンスが最適化されたこのコードでそれほど大きな最適化が不要になることもありますが、厳密なテストがなければ、それを確実に知る方法はありません。
ここでの問題は、「最適化」を読み取り可能および拡張可能から区別することです。ユーザーが最適化コードと見なすものと、コンパイラが最適化と見なすものは、2つの異なるものです。変更しようとしているコードはボトルネックではない可能性があるため、コードが「リーン」であっても、「最適化」する必要はありません。または、コードが十分古い場合は、コンパイラーが組み込みに最適化を行って、新しい単純な組み込み構造を古いコードと同等以上の効率で使用できるようにする場合があります。
そして、「リーン」で読めないコードは常に最適化されるとは限りません。
私は以前、賢い/無駄のないコードは優れたコードであると考えていましたが、コードの作成に役立つのではなく、言語のあいまいなルールを利用することで、埋め込み作業ではなく、コンパイラーが賢いコードを組み込みハードウェアでまったく使用できないものにするので、賢くなります。
パフォーマンスに妥協できないため、最適化されたコードを読み取り可能なコードに置き換えることは絶対にありません。両方の問題を解決する最適化されたセクションに実装されたロジックを誰でも理解できるように、すべてのセクションで適切なコメントを使用することにします。
したがって、コードは最適化されます+適切なコメントは、それも読みやすくします。
注:適切なコメントを使用して最適化コードを読み取り可能にすることはできますが、読み取り可能コードを最適化コードにすることはできません。
簡単なコードと最適化されたコードの違いを確認する例を次に示します。 https://stackoverflow.com/a/11227902/1396264
答えの終わりに向かって、彼は単に置き換えます:
if (data[c] >= 128)
sum += data[c];
と:
int t = (data[c] - 128) >> 31;
sum += ~t & data[c];
公平を期すために、ifステートメントがどのように置き換えられたかはわかりませんが、回答者がビット単位の演算を実行すると、同じ結果が得られます(私は彼のWordをそのまま使用します)。
これは元の時間の4分の1未満で実行されます(11.54秒vs 2.5秒)
ここでの主な質問は、最適化が必要かどうかです。
もしそうなら、それをより遅く、より読みやすいコードに置き換えることはできません。読みやすくするためにコメントなどを追加する必要があります。
コードを最適化する必要がない場合は、コードを(可読性に影響を与えるほど)最適化しないでください。コードをリファクタリングして、読みやすくすることができます。
ただし、コードを変更する前に、コードが何を行うか、どのように徹底的にテストするかを正確に把握してください。これにはピーク使用量などが含まれます。テストケースのセットを作成して前後に実行する必要がない場合は、リファクタリングを実行する時間がありません。
これは私が物事を行う方法です:最初にそれを可読コードで機能させるようにし、次にそれを最適化します。元のソースを保持し、最適化手順を文書化します。
次に、機能を追加する必要がある場合は、可読コードに戻り、機能を追加して、文書化した最適化手順に従います。あなたが文書化したので、新機能でコードを再最適化するのは本当に速くて簡単です。
最適化は相対的です。例えば:
多数のBOOLメンバーを持つクラスを考えます。
// no nitpicking over BOOL vs bool allowed
class Pear {
...
BOOL m_peeled;
BOOL m_sliced;
BOOL m_pitted;
BOOL m_rotten;
...
};
BOOLフィールドをビットフィールドに変換したくなるかもしれません:
class Pear {
...
BOOL m_peeled:1;
BOOL m_sliced:1;
BOOL m_pitted:1;
BOOL m_rotten:1;
...
};
BOOLはINTとしてtypedefされているため(Windowsプラットフォームでは、符号付き32ビット整数です)、これは16バイトを取り、それらを1バイトにパックします。これは93%の節約になります。誰がそれについて文句を言うのでしょうか?
この仮定:
BOOLはINTとしてtypedefされているため(Windowsプラットフォームでは、符号付き32ビット整数です)、これは16バイトを取り、それらを1バイトにパックします。これは93%の節約になります。誰がそれについて文句を言うのでしょうか?
につながる:
BOOLをシングルビットフィールドに変換すると、3バイトのデータが節約されますが、メンバーに非定数値が割り当てられると、8バイトのコードがかかります。同様に、値の抽出はより高価になります。
昔は
Push [ebx+01Ch] ; m_sliced
call _Something@4 ; Something(m_sliced);
なる
mov ecx, [ebx+01Ch] ; load bitfield value
shl ecx, 30 ; put bit at top
sar ecx, 31 ; move down and sign extend
Push ecx
call _Something@4 ; Something(m_sliced);
ビットフィールドバージョンは9バイト大きくなります。
座って計算をしてみましょう。これらのビットフィールドフィールドのそれぞれに、コードで6回、書き込みで3回、読み取りで3回アクセスするとします。コードの増加に伴うコストは約100バイトです。オプティマイザが一部の操作ですでにレジスタにある値を利用できる場合があるため、正確に102バイトになるわけではなく、追加の命令により、レジスタの柔軟性が低下するという隠れたコストが発生する場合があります。実際の差はもっと大きくても少なくてもかまいませんが、エンベロープの逆算の計算では100としましょう。一方、メモリの節約はクラスあたり15バイトでした。したがって、損益分岐点は7です。プログラムがこのクラスのインスタンスを7つ未満作成した場合、コードのコストはデータの節約量を上回ります。メモリーの最適化はメモリーの最適化解除でした。
参照
IMHOの読みやすさは最適化されたコードよりも重要です。これは、ほとんどの場合、マイクロ最適化は価値がないためです。
私たちのほとんどと同じように、printをechoで置き換える、++ $ iを$ i ++で置き換える、二重引用符を一重引用符で置き換えるなど、意味のないマイクロ最適化に関するブログ投稿を読むのはうんざりです。どうして?時間の99.999999%のため、それは無関係です。
"print"は実際に何かを返すため、 "echo"よりも1つ多いオペコードを使用します。エコーは印刷よりも速いと結論付けることができます。しかし、1つのオペコードのコストはまったくかかりません。
新しいWordPress=インストールを試しました。ラップトップで「バスエラー」で終了する前にスクリプトが停止しましたが、オペコードの数はすでに230万を超えていました。