AOPの理論と実践。 Yandexでの方法

Yandexで働く主な機能の1つは、テクノロジーを選択する自由です。 私が働いているAvto.ruでは、歴史的な決定の大きなレイヤーをサポートする必要があるため、新しいテクノロジーやライブラリは同僚からの2つの質問に答えます。



-これにより、分布はどの程度増加しますか?

「これはどのように私たちがより少ない効率で書くのに役立ちますか?







現在、 RxJavaDagger 2Retrolambda 、およびAspectJを使用しています。 そして、すべての開発者が最初の3つの技術について耳にし、多くの人が自宅でそれらを使用する場合でも、大規模なサーバープロジェクトやさまざまな種類の企業を作成するハードコアジャビストのみが4番目の技術について知っています。



私の目標は、これら2つの質問に答え、AndroidプロジェクトでAOP方法論を使用することを正当化することでした。 そしてこれは、コードを記述し、アスペクト指向プログラミングが開発者の作業をスピードアップして促進するのにどのように役立つかを示すことを意味します。 しかし、まず最初に。







基本から始めましょう



すべてのAPIリクエストをトライケッチでラップして、失敗しないようにします! また、ログ! そしてまた...

Pff ... 7行のコードと出来上がりを記述します。

abstract aspect NetworkProtector { //  ,    abstract pointcut myClass(); // ,   —      Response around(): myClass() && execution(* executeRequest(..)) { //  «»  executeRequest try { return proceed(); //    ,  around' } catch (NetworkException ex) { Response response = new Response(); //       ... response.addError(new Error(ex)); // …    ,  ,   return response; } } }
      
      







簡単ですね。 そして今、少し用語がありますが、それなしではそれ以上の方法はありません。



アスペクトプログラミングは、コードを個別の独立したモジュールに分離することです。 通常のオブジェクトアプローチでは、このコードはアプリケーション全体(またはその重要な部分)に浸透し、コンポーネントの純粋なロジックの不純物として、すべてのステップで満たされます。 このような不純物には、永続性、アクセス制御、ロギングとプロファイリング、マーケティングおよび開発分析が含まれます。



開発者がZenを最初に理解し始めるのは、同質性の検索です。 2つのクラスが同じ種類の作業を行う場合、たとえば、同じオブジェクトを操作する場合、それらは同種です。 n個のエンティティが外界とまったく同じように相互作用する場合-それらは同質です。 これはすべてスライス(ポイントカット)で記述でき、啓発への魅力的なパスを開始できます。



2つ目は、賢明なエンジニアがそれなしではできないことです。 ランダムな均質性のすべての可能な組み合わせを提供することを念頭に置いています。 トピックや状況に関連しないオブジェクトは、あなたの条件に該当する場合があります。 それらは、あるトリッキーな角度で似ていることを考慮していないだけです。 これにより、永続的なパターンの記述方法がわかります。



注釈付きのスライスの説明を開始するのが最善です。 そして、率直に言って、それらを完成させる方が良いです。 これは、5番目のJavaから生まれたすばらしい明白なアプローチです。 このクラスで超越的な魔法が進行していることを未開拓のエンジニアに伝えるのは注釈です。 内部のAspectJが決定するのは、Springフレームワークの第2の中心である注釈です。 最新の大規模プロジェクト(AndroidAnnotations、Dagger、ButterKnife)はすべて同じ道をたどります。 なんで? 証拠と簡潔さ、カール。 証拠と簡潔さ。



おっと



ツールキット



開発兵器について個別に簡単に話しましょう。 Androidには、非常に多くのツールと方法論、アーキテクチャアプローチ、およびさまざまなコンポーネントがあります。 ミニマルヘルパーライブラリとRealmのような巨大なハーベスタがあります。 そして、比較的小さいが深刻なレトロフィット、ピカソ。

この多様性をすべてプロジェクトに適用し、コードだけでなく、新しいアーキテクチャの側面やライブラリにも適応します。 独自のスキルを向上させ、新しいツールを理解して習得します。 そして、このツールが大きくなればなるほど、真剣に再学習する必要があります。



この適応は、Kotlinの人気の高まりによって明確に示されています。Kotlinを使用するには、ツールとしてではなく、プロジェクト全体のアーキテクチャおよび構造に対するアプローチの変更が必要です。 この言語のアスペクトアプローチの糖不純物(メソッドとフィールドの拡張を暗示しています)は、ビジネスロジックと永続性の構築に柔軟性を追加しますが、プロセスの理解を鈍らせます。 コードがデバイス上でどのように機能するかを「見る」ためには、現在表示されているコードだけでなく、外部からの命令とデコレータをその中にミックスする必要があります。



AOPに関しても同じ状況です。



問題と解決策の選択



特定の状況により、適切で可能な(またはそうでない)ソリューションのセットが決まります。 自分の経験と知識に頼って、頭の中で解決策を探すことができます。 または、知識が特定の問題を解決するのに十分でない場合は、助けを求めてください。

完全に明白で単純な「タスク」の例は、ネットワーク層です。 以下が必要です。



また、RxJavaまたはEventBusを使用したことがない場合は、この問題の解決策により、多くの水中レーキが発生します。 同期からライフサイクルまで。



数年前、Rxを知っていたAndroid開発者はほとんどいませんでしたが、今では人気が高まっており、すぐに仕事の説明の必須項目になる可能性があります。 何らかの形で、私たちは常に自分自身を開発し、新しい技術、便利な慣行、ファッショントレンドに適応します。 彼らが言うように、習熟には経験が伴います。 一見しただけでは本当に必要ではなかったとしても:)



新しい視野、またはAOPが必要な理由



アスペクト環境では、根本的に新しい概念である同質性が見られます。 例ではすぐに、さらに苦労せずに。 しかし、Androidからそれほど遠くはありません。



 public class MyActivityImpl extends Activity { protected void onCreate(Bundle savedInstanceState) { TransitionProvider.overrideWindowTransitionsFor(this); super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_main); Toolbar toolbar = ToolbarProvider.setupToolbar(this); this.setActionBar(toolbar); AnalyticsManager.register(this); } }
      
      







ほとんどすべての画面とフラグメントに同様の定型文を作成します。 個々の手順は、プロバイダー、プレゼンテーション、またはオンラインインタラクターで定義できます。 また、システムコールバックで「群衆化」できます。

これらすべてが美しく体系的な形をとるために(「システム化」という言葉から)、まずこれについて慎重に考えます。このようなロジックをどのように分離できるのでしょうか。 ここでの良い解決策は、いくつかの個別のクラスを作成することです。各クラスは、独自の小さな部分を担当します。



最初にツールバーの動作を分離します
 public aspect ToolbarDecorator { pointcut init(): execution(* Activity+.onCreate(..)) && //      Activity @annotation(StyledToolbarAnnotation); //        after() returning: init() { //    ,  onCreate  Activity act = thisJoinPoint.getThis(); Toolbar toolbar = setupToolbar(act); act.setActionBar(toolbar); } }
      
      







オーバーライドアクティビティアニメーションを取り除く
 public aspect TransitionDecorator { pointcut init(TransitionAnnotation t): @within(t) && //   execution(* Activity+.onCreate(..)); //   before(TransitionAnnotation transition): init(transition) { Activity act = thisJoinPoint.getThis(); registerState(transition); overrideWindowTransitionsFor(act); } }
      
      







そして最後に-分析を別のクラスにスローします
 public aspect AnalyticsInjector { private static final String API_KEY = “…”; pointcut trackStart(): execution(* Activity+.onCreate(..)) && @annotation(WithAnalyticsInit); after(): returning: trackStart() { Context context = thisJoinPoint.getThis(); YandexMetrica.activate(context, API_KEY); Adjust.onCreate(new AdjustConfig(context, “…”, PROD)); } }
      
      









まあ、それだけです。 クリーンでコンパクトなコードが得られました。同種の機能の各部分が美しく分離され、アクティビティから継承しようとするすべてのクラスではなく、明らかに必要な場所にのみ固定されます。

最終ビュー:

 @StyledToolbarAnnotation @TransitionAnnotation(TransitionType.MODAL) @WithContentViewLayout(R.layout.activity_main) //    AndroidAnnotations! \m/ public class MyActivityImpl extends Activity { @WithAnalyticsInit protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* ... */ } }
      
      







この例では、注釈は均質性のソースであり、その助けを借りて、追加機能を文字通りどこでも「固定」できます。 注釈、自己文書化された名前、および創意工夫を組み合わせることにより、オブジェクトコード全体に沿って同種性を求めたり宣言したりします。



注釈のおかげで、コードで発生するプロセスの理解が保持されます。 初心者は、すぐにエンジンフードの魔法があることに気付くでしょう。 自己文書化により、ロギング、プロファイリングなどのサービスツールを簡単に管理できます。 Javaコードのインスツルメンテーションは、アクセスと使用を追跡するクラス、メソッド、またはフィールドの名前でキーワードの出現を検索するように簡単に構成できます。



アスペクトの適用の非標準的な側面について。


大規模なチームは、多くの場合、コードが多くの段階を通過する厳密なフローコミットを構築します。 CIでのテストビルド、コードインスペクション、テストでの実行、プルリクエストがあります。 追加のソフトウェアをインストールする必要のない静的コード分析を導入することで、品質を損なうことなく、このプロセスの反復回数を減らすことができます。開発者にlintレポートを強制するか、このケースを同じsvcの側に持ちます。



コンパイラーにディレクティブを記述するだけで十分です。コンパイラーは、コードで「誤って」または「潜在的に悪い」ことを正確に行うことができます。



セッターメソッド外のフィールドレコードの簡単なチェック
 public aspect AccessVerifier { declare warning : fieldSet() && within(ru.yandex.example.*) : "writing field outside setter" ; pointcut fieldSet(): set(!public * *) && !withincode(* set*(..)); // set      ,    —  - }
      
      









より深刻な状況では、開発者が明らかに「ジャグリング」している場合、または明らかにそうでない場合に動作を変更しようとしている場合、プロジェクトの組み立てを完全に拒否できます。



NPEトラップを確認し、ビルドメソッド外でコンストラクターを呼び出す
 public aspect AnalyticsVerifier { declare error : handler(NullPointerException+) //  try-catch    NPE && withincode(* AnalyticsManager+.register(..)) : "do not handle NPE in this method"; declare error : call(AnalyticsManager+.new(..)) && !cflow(static AnalyticsManager.build(..)) : "you should not call constructor outside a AnalyticsManager.build() method"; }
      
      





マジックワード「cflow」は、ターゲットメソッドの実行内の任意の深さでのすべてのネストされた呼び出しのキャプチャです。 あまり明白ではないが、非常に強力なこと。





順序は私にとって重要です! 何かが時間通りに機能しない場合
 public aspect StrictVerifyOrder { //  /,      declare precedence: *Injector, *Decorator, *Verifier, *; //     ,  ! }
      
      





それは人々がしばしばそれについて尋ねるだけです:)はい、あなたはペンを使って個々の側面の「重要性」と順序を調整することができます。

しかし、それを各クラスに押し込むべきではありません。さもなければ、順序は予測不可能になります(あなたの上限です!)。





結論



すべてのタスクは、最も便利なツールによって解決されます。 アスペクト指向の開発アプローチを使用して簡単に解決できるいくつかの簡単な日常タスクを強調しました。 これは、OOPを放棄して何か他のものを学ぶための呼び出しではなく、逆です! 熟練した手で、AOPはオブジェクト構造を調和して拡張し、実証済みのソリューションを使用する際の分離、コード重複排除、コピーペースト、ガーベッジ、不注意への容易な対処の問題を解決します。



1つの短いクラスを10行で記述し、透明で単純な「アンカー」または条件を使用して、それを他の12個のクラスに実装します。 同時に、安定したアスペクトクラスを記述するコストは低くなり、コードの均一性の検索と適用により早く適応できます。



All Articles