内偎から倖偎のパむリント。 圌のやり方

クヌルなコヌドを蚘述するさたざたなアシスタントが、私たち、リンタヌ、タむマヌ、脆匱性を怜玢するためのナヌティリティをすべお私たちず共に取り囲んでいたす。 私たちはそれに慣れおおり、「ブラックボックス」のような詳现に入らずに䜿甚しおいたす。 たずえば、Pythonコヌドの最適化ず改善に䞍可欠なツヌルの1぀であるPylintの原理を理解しおいる人はほずんどいたせん。

しかし、 マキシムマザ゚フは自分のツヌルを理解するこずがいかに重芁かを知っおおり、 モスクワPython Conf ++で私たちに語った。 実際の䟋を䜿甚しお、圌はPylintの内郚デバむスずそのプラグむンの知識がコヌドレビュヌ時間の短瞮、コヌド品質の向䞊、そしお䞀般的に開発効率の向䞊にどのように圹立぀かを瀺したした。 以䞋は埩号化呜什です。



なぜPylintが必芁なのですか


既にそれを䜿甚しおいる堎合、「Pylintの内郚に䜕があるのか​​、なぜこの知識が圹立぀のか」ずいう疑問が生じるかもしれたせん。

通垞、開発者はコヌドを蚘述し、リンタヌを起動し、改善すべき点、コヌドをより矎しくする方法に぀いおのメッセヌゞを受け取り、提案された倉曎を行いたす。 これで、コヌドが読みやすくなり、同僚に芋せるこずを恥じるこずがなくなりたした。

長い間、これは圌らがCIANのPylintで正確に機胜する方法でしたが、マむナヌな远加がありたした構成を倉曎し、䞍芁なルヌルを削陀し、最倧文字列長を増やしたした。

しかし、ある時点で圌らは問題に盎面したした。その解決のために、私はPylintの奥深くを掘り䞋げお、それがどのように機胜するかを理解しなければなりたせんでした。 この問題ずそれを解決する方法は䜕ですか、読んでください。


スピヌカヌに぀いお開発䞭の5幎間、Maxim Mazaev バックスラッシュ はCIANで働いおいたす。 Python、非同期、および関数型プログラミングを深く孊びたす。

シアンに぀いお


CIANは䞍動産業者のいる䞍動産䌚瀟であり、䞍動産業者の代わりにプログラマヌがいるこずに気づいたずき、ほずんどの人が信じおいたす。

私たちは党米リアルタヌ協䌚加入者がいない技術䌚瀟ですが、倚くのプログラマヌがいたす。


毎日、数癟、数千行の新しいコヌドが生産されおいたす。 コヌドの芁件は非垞に簡単です。


もちろん、これを実珟するには、コヌドレビュヌが必芁です。

コヌドレビュヌ


CIANのコヌドレビュヌは2段階で行われたす。

  1. 最初の段階は自動化されおいたす。 Jenkinsロボットはテストを実行し、Pylintを実行し、マむクロサヌビスを䜿甚しおいるため、マむクロサヌビス間のAPIの䞀貫性をチェックしたす。 この段階でテストが倱敗するか、リンタヌに奇劙な䜕かが衚瀺された堎合、これはプル芁求を拒吊し、修正のためにコヌドを送信する機䌚です。
  2. 最初の段階が成功した堎合、2番目の段階-2 人の 開発者からの承認。 圌らは、ビゞネスロゞックの芳点からコヌドがどれだけ優れおいるかを評䟡したり、プルリク゚ストを承認したり、修正のためにコヌドを返したりするこずができたす。


コヌドレビュヌの問題


次の理由により、プルリク゚ストはコヌドレビュヌに合栌しない堎合がありたす。


リンタヌがコヌドをチェックする堎合、スタむルの問題は䜕ですか

Pythonで曞く人は誰でも、 PEP-8コヌドを曞くためのガむドがあるこずを知っおいたす。 他の暙準ず同様に、PEP-8は非垞に䞀般的であり、開発者ずしおの私たちにずっおこれは十分ではありたせん。 䞀郚の堎所で暙準を指定し、他の堎所で拡匵したい。

そのため、コヌドの倖芳ず動䜜を内郚で調敎し、 「Decline Cian Proposals」ず呌びたした。



「Decline Cian Proposals」-䞀連のルヌル、珟圚は玄15ありたす。これらの各ルヌルは、プルリク゚ストが拒吊され、修正のために送信されるための基瀎です。

生産的なコヌドレビュヌを劚げるものは䜕ですか


私たちの内郚ルヌルには1぀の問題がありたす-リンタヌはそれらに぀いお知りたせん、そしお圌が知っおいたら奇劙です-それらは内郚です。
タスクを実行する開発者は、垞にルヌルを芚えおおく必芁がありたす。 ルヌルの1぀を忘れるず、コヌドレビュヌのプロセスでレビュアヌが問題を指摘し、タスクは改蚂に進み、タスクのリリヌス時間が長くなりたす。 ゚ラヌの修正ず修正の埌、テスタヌはタスクの内容を芚えお、コンテキストを切り替える必芁がありたす。

これにより、開発者ずレビュヌアの䞡方に問題が発生したす。 その結果、コヌドレビュヌの速床が倧幅に䜎䞋したす。 コヌドのロゞックを分析する代わりに、テスタヌは芖芚スタむルの分析を開始したす。぀たり、リンタヌの䜜業を実行したす。぀たり、コヌドを1行ず぀スキャンし、むンポヌト圢匏のむンデントの䞍䞀臎を探したす。

この問題を取り陀きたいです。

しかし、私たちにあなたのリンタヌを曞いおはいけたせんか


この問題は、すべおの内郚合意に぀いお知っおいるツヌルによっお解決され、実装のコヌドをチェックできるようになりたす。 独自のリンタヌが必芁ですか

そうでもない。 すでにPylintを䜿甚しおいるため、このアむデアは銬鹿げおいたす。 これは䟿利なリンタヌであり、開発者に奜たれ、すべおのプロセスに組み蟌たれおいたす。ゞェンキンスで実行され、完党に満足のいく矎しいレポヌトを生成し、コメントの圢でリク゚ストをプルしたす。 すべおが正垞で、 2番目のリンタヌは必芁ありたせん 。

それでは、自分のリンタヌを曞きたくない堎合、どのように問題を解決するのでしょうか

Pylintプラグむンを曞く


Pylint甚のプラグむンを䜜成できたす。これらはチェッカヌず呌ばれたす。 各内郚ルヌルの䞋で、それをチェックする独自のチェッカヌを䜜成できたす。

そのようなチェッカヌの2぀の䟋を考えおみたしょう。

䟋1


ある時点で、コヌドには「TODO」ずいう圢匏の倚くのコメントが含たれおいるこずが刀明したした。リファクタリング、䞍芁なコヌドの削陀、たたは矎しく曞き盎すこずを玄束したす。 そのようなコメントには問題がありたす-圌らはあなたに䜕も矩務付けたせん。

問題


開発者は玄束を曞き、吐き出し、安心しお次のタスクを実行したした。


芁玄するず


たずえば、3幎前の開発者は、リリヌスが成功した埌に䜕かを削陀するこずを玄束したしたが、リリヌスは3幎で行われたしたか たぶんはい。 この堎合、コヌドを削陀する必芁がありたすか これは倧きな質問ですが、おそらくそうではありたせん。

解決策Pylintのチェッカヌを䜜成する


開発者がそのようなコメントを曞くこずを犁止するこずはできたせんが、远加の䜜業をさせるこずができたす。トラッカヌでタスクを䜜成しお玄束を確定したす。 その埌、私たちは圌女のこずを間違いなく忘れたせん。

TODOフォヌムのすべおのコメントを芋぀け、各コメントにJiraのタスクぞのリンクがあるこずを確認する必芁がありたす。 曞きたしょう。

Pylintに関するチェッカヌずは䜕ですか これは、チェッカヌの基本クラスから継承し、特定のむンタヌフェむスを実装するクラスです。

class TodoIssueChecker(BaseChecker): _ _implements_ _ = IRawChecker 

私たちの堎合、これはIRawChecker-いわゆる「生」チェッカヌです。

生のチェッカヌはファむルの行を反埩凊理し、行に察しお特定のアクションを実行できたす。 この堎合、各行でチェッカヌはコメントずタスクぞのリンクに䌌たものを探したす。

チェッカヌの堎合、発行するメッセヌゞのリストを決定する必芁がありたす。

 msgs = { '9999': ('  TODO    ', issue-code-in-todo', ' ')} 

メッセヌゞには次のものがありたす。


メッセヌゞコヌドの圢匏は「C1234」です。


チェックが䞍芁になった堎合、チェックを無効にするためにコヌドが必芁です。 Pylintdisableず短い英数字コヌドたたはニヌモニック名を曞くこずができたす

 # Pylint: disable=C9999 # Pylint: disable=issue-code-in-todo 

Pylintの著者は、英数字コヌドを攟棄し、ニヌモニックを䜿甚するこずを掚奚しおいたす。これはより芖芚的です。

次のステップは、 process_moduleずいうメ゜ッドを定矩するこずです。



名前は非垞に重芁です。 Pylintがメ゜ッドを呌び出すため、メ゜ッドはそのように呌び出す必芁がありたす。

nodeパラメヌタヌはモゞュヌルに枡されたす。 この堎合、それが䜕であるか、たたはどのタむプであるかは関係ありたせん。ノヌドには、行ごずにファむルを返すストリヌムメ゜ッドがあるこずを芚えおおくこずが重芁です。

ファむルを調べお、各行でコメントずタスクぞのリンクを確認できたす。 コメントはあるがリンクがない堎合は、チェッカヌコヌドず行番号を含む「issue-code-in-todo」フォヌムの譊告をスロヌしたす。 アルゎリズムは非垞に簡単です。

チェッカヌを登録しお、Pylintが認識できるようにしたす。 これは、 登録機胜によっお行われたす。

 def register(linter: Pylinter) -> None: linter. register_checker ( TodoIssueChecker(linter) ) 


重芁なポむントチェッカヌモゞュヌルはPylthonpathに存圚しおいる必芁がありたす。これにより、Pylintは埌でそれをむンポヌトできたす。

登録されたチェッカヌは、タスクぞのリンクのないコメント付きのテストファむルによっおチェックされたす。

 $ cat work. # T0D0:   , -! $ pylint work. --load-plugins todo_checker 
 

テストのために、Pylintを実行し、モゞュヌルを枡し、 load-pluginsパラメヌタヌを䜿甚しおチェッカヌを転送し、リンタヌ内で2぀のフェヌズを実行したす。

フェヌズ1.プラグむンの初期化



フェヌズ2.チェッカヌのプヌルを解析する


フェヌズ1の埌、さたざたな皮類のチェッカヌの完党なリストが残りたす。




リストから、未加工のチェッカヌむンタヌフェむスに関連するものを遞択したす。どのチェッカヌがIRawCheckerむンタヌフェむスを実装しおいるかを確認し、それらを自分甚に取りたす。

遞択したチェッカヌごずに、 checker.process_moduleモゞュヌルメ゜ッドを呌び出しお、チェックを実行したす。

結果


テストファむルでチェッカヌを再床実行したす。

 $ cat work. # T0D0:   , -! $ pylint work,  --load-plugins todo_checker : 0,0:   T0D0     (issue-code-in-todo) 

TODOを含むコメントがあり、タスクぞのリンクがないこずを瀺すメッセヌゞが衚瀺されたす。

この問題は解決され、コヌドレビュヌの過皋で、開発者は目でコヌドをスキャンしたり、コメントを芋぀けたり、コヌド䜜成者に同意があり、リンクを残すこずをお勧めするこずを思い出させる必芁はありたせん。 すべおが自動的に行われ、コヌドのレビュヌが少し速くなりたす。

䟋2。キヌワヌド匕数


䜍眮匕数を取る関数がありたす。 倚数の匕数がある堎合、関数を呌び出すずきに、匕数がどこにあるのか、なぜ必芁なのかは明確ではありたせん。

問題


たずえば、次の関数がありたす。

 get_offer_by_cian_id( "sale", rue, 859483, ) 

コヌドにはセヌルずTrueがあり、それらの意味が明確ではありたせん。 倚くの匕数がある関数が名前付き匕数でのみ呌び出される堎合は、はるかに䟿利です。

 get_offer_by_cian_id( deal_type="sale", truncate=True, cian_id=859483, ) 

これは優れたコヌドであり、パラメヌタがどこにあるかがすぐにわかり、シヌケンスを混同したせん。 そのような堎合をチェックするチェッカヌを䜜成しおみたしょう。

前の䟋で䜿甚した「生の」チェッカヌは、このような堎合に曞くのは非垞に困難です。 超耇雑な正芏衚珟を远加できたすが、そのようなコヌドは読みにくいです。 Pylintが抜象AST構文ツリヌに基づいお別のタむプのチェッカヌを蚘述できるようにしおくれるのは良いこずであり、これを䜿甚したす。

ASTに関する歌詞の䜙談


ASTたたは抜象構文ツリヌはコヌドのツリヌ衚珟であり、頂点はオペランドであり、リヌフは挔算子です。

たずえば、1぀の䜍眮匕数ず2぀の名前付き匕数がある関数呌び出しは、抜象ツリヌに倉換されたす。


Call型の頂点があり、次の芁玠がありたす。


この堎合のタスク



 ll( func=Name(name='get_offer'), args=[Const(value=1298880)], keywords=[ 
 ]))] 

Pylintの芳点から芋るず、ASTベヌスのチェッカヌはベヌスチェッカヌクラスを継承し、 IAstroidCheckerむンタヌフェむスを実装するクラスです。

 class NonKeywordArgsChecker(BaseChecker): -_ _implements_ _ = IAstroidChecker 

最初の䟋のように、チェッカヌの説明、メッセヌゞコヌド、短いニヌモニック名がメッセヌゞリストに衚瀺されたす。

 msgs = { '9191': (' ', keyword-only-args', ' ')} 

次のステップはvisit_callメ゜ッドの定矩です

 def visit_call(self, node: Call) 
 

このメ゜ッドを呌び出す必芁はありたせん。 その䞭で最も重芁なのはvisit_接頭蟞であり、それから私たちの興味を匕く頂点の名前に小さな文字が付きたす。


この䟋では、visit_callメ゜ッドは入力ずしおCallタむプノヌドを受け取り、3぀以䞊の匕数があるかどうか、および譊告をスロヌしおコヌドをノヌド自䜓に枡すための䜍眮匕数があるかどうかを確認したす。

 def visit_call(self, n): if node.args and len(node.args + node.keywords) > 2: self.add_message( 'keyword-only-args', node=node ) 

前の䟋のようにチェッカヌを登録したす。Pylintむンスタンスを転送し、register_checkerを呌び出し、チェッカヌ自䜓を枡しお起動したす。

 def register(linter: Pylinter) -> None: linter.register_checker( TodoIssueChecker(linter) ) 

これは、3぀の匕数があり、そのうちの1぀だけに名前が付けられおいるテスト関数呌び出しの䟋です。

 $ cat work. get_offers(1, True, deal_type="sale") $ Pylint work.py --load-plugins non_kwargs_checker 
 

これは、私たちの芳点から誀っお呌び出される可胜性のある関数です。 Pylintを起動したす。

前の䟋のように、プラグむンの初期化フェヌズ1は完党に繰り返されたす。

フェヌズ2. ASTでのモゞュヌルの分析


コヌドは解析されおASTツリヌになりたす。 分析はAstroidラむブラリによっお実行されたす 。

ASTではなくAstroidを䜿甚する理由stdlib


Astroidは内郚的には暙準のPython ASTモゞュヌルを䜿甚したせんが、 typed_ast型付きASTパヌサヌは、PEP 484 型ヒントをサポヌトするずいう点で異なりたす。 興味深いこずに、ASTには同じバグがあり、䞊行しお修埩されたす。

 from module import Entity def foo(bar): # type: (Entity) -> None return 

以前は、Astroidは暙準のASTモゞュヌルを䜿甚しおいたした。このモゞュヌルでは、2番目のPythonで䜿甚されおいるコメントで定矩されおいるタむフィントを䜿甚する問題に遭遇する可胜性がありたす。 Pylintを䜿甚しおこのようなコヌドをチェックするず、むンポヌトされたEntityクラスはコメント内にのみ存圚するため、特定の時点たでは未䜿甚のむンポヌトを誓いたす。

ある時点で、Guido Van RossumはGitHubでAstroidに来お次のように蚀いたした。 友達になろう」

仕事が始たった 2幎が経ち、この春のPylintは既に型付きASTパヌサヌに切り替え、そのようなこずを誓うこずをやめたした。 taiphintsのむンポヌトは、未䜿甚ずしおマヌクされなくなりたした。

AstroidはASTパヌサヌを䜿甚しおコヌドをツリヌに解析し、構築時に興味深いこずを行いたす。 たずえば、 import *を䜿甚する堎合、すべおをアスタリスクでむンポヌトし、ロヌカルに远加しお、未䜿甚のむンポヌトでの゚ラヌを防ぎたす。

倉換プラグむンは、すべおの属性が動的に生成されるメタクラスに基づいたいく぀かの耇雑なモデルがある堎合に䜿甚されたす。 この堎合、Astroidの意味を理解するのは非垞に困難です。 チェックするず、Pylintは、モデルにアクセスしたずきにそのような属性がないこずを誓い、Transformプラグむンを䜿甚しお問題を解決できたす。


兞型的な䟋は、 pylint-djangoです。 耇雑なdjangoモデルで䜜業するずき、リンタヌはしばしば未知の属性を誓いたす。 Pylint-djangoはこの問題を解決するだけです。

フェヌズ3.チェッカヌのプヌルを解析する


チェッカヌに戻りたす。 再床チェッカヌのリストがあり、そこからASTチェッカヌむンタヌフェむスを実装するチェッカヌが芋぀かりたす。

フェヌズ4.ノヌドのタむプ別にチェッカヌを解析する


次に、各チェッカヌのメ゜ッドを芋぀けたす。2぀のタむプがありたす。


ツリヌ内を歩いおいるずきにノヌドを呌び出す必芁があるノヌドを知っおいるず䟿利です。 したがっお、キヌはノヌドの名前であり、倀はこのノヌドぞのアクセスの事実に関心があるチェッカヌのリストである蟞曞を理解したす。

 _visit_methods = dict( < > : [checker1, checker2 ... checkerN] ) 

leave-methodsでも同じです。ノヌド名の圢匏のキヌ、このノヌドからの退出の事実に関心のあるチェッカヌのリスト。

 _leave_methods = dict( < >: [checker1, checker2 ... checkerN] ) 

Pylintを起動したす。 3぀以䞊の匕数があり、その䞭に䜍眮匕数がある関数があるずいう譊告を瀺しおいたす。

 $ cat work. get_offers(1, True, deal_type="sale") $ Pylint work.py --load-plugins non_kwargs_checker C: 0, 0:  c >2      (keyword-only-args) 

問題は解決したした。 これで、コヌドレビュヌプログラマヌは関数の匕数を読み取る必芁がなくなり、リンタヌがこれを行いたす。 本番環境での時間、コヌドのレビュヌ、タスクの時間を節玄したした。

そしお、テストを曞くために


Pylintを䜿甚するず、チェッカヌの単䜓テストを実行でき、非垞に簡単です。 リンタヌの芳点から芋るず、テストチェッカヌは抜象CheckerTestCaseを継承するクラスのように芋えたす。 チェックむンされおいるチェッカヌを瀺す必芁がありたす。

 class TestNonKwArgsChecker(CheckerTestCase): CHECKER_CLASS = NonKeywordArgsChecker 

ステップ1.チェックするコヌドの䞀郚からテストASTノヌドを䜜成したす。

 node = astroid.extract_node( "get_offers(3, 'magic', 'args')" ) 

手順2.ノヌドに入るチェッカヌが、察応するメッセヌゞをスロヌするかしないかを確認したす。

 with self.assertAddsMessages(message): self.checker.visit_call(node) 

トヌクンチェッカヌ


TokenCheckerず呌ばれる別のタむプのチェッカヌがありたす。 字句解析の原理で動䜜したす。 Pythonには、字句スキャナヌの機胜を実行し、コヌドをトヌクンのリストに分割するtokenizeモゞュヌルがありたす。 次のようになりたす。


倉数名、関数名、およびキヌワヌドはタむプNAMEのトヌクンになり、区切り文字、括匧、コロンはタむプOPのトヌクンになりたす。 さらに、むンデント、改行、逆倉換甚の個別のトヌクンがありたす。

PylintがTokenCheckerず連携する方法


TokenCheckerの䜿甚は芋぀かりたせんでしたが、Pylintが䜿甚するそのような䟋がありたす。


結論


コヌドレビュヌに問題がありたした。 開発者はリンタヌの仕事をし、無意味なコヌドスキャンに時間を費やし、䜜者に゚ラヌを通知したした。 Pylintの堎合


簡単なチェッカヌは30分で曞かれ、耇雑なチェッカヌは数時間で曞かれたす。 チェッカヌは、曞き蟌みにかかる時間よりもはるかに倚くの時間を節玄し、拒吊されおいないいく぀かのプルリク゚ストに察凊したす。

Pylintの詳现ずチェッカヌの䜜成方法に぀いおは公匏ドキュメントで孊ぶこずができたすが、チェッカヌの䜜成ずいう点ではかなり貧匱です。 たずえば、TokenCheckerに぀いおは蚀及されおいるだけですが、チェッカヌ自䜓の蚘述方法に぀いおは蚀及されおいたせん。 詳现に぀いおは、GitHubのPylint゜ヌスを参照しおください 。 暙準パッケヌゞに含たれおいるチェッカヌを確認し、独自のチェッカヌを䜜成するためのむンスピレヌションを埗るこずができたす。

Pylintの内郚蚭蚈に関する知識により、工数が削枛され、簡玠化されたす
パフォヌマンスずコヌドの改善。 時間を節玄し、良いコヌドを曞き、
リンタヌを䜿甚したす。
次のモスクワPython Conf ++カンファレンスは2019幎4月5日に開催され 、今では早期birfチケットを予玄できたす。 意芋を集めおレポヌトに申し蟌むず 、蚪問が無料になり、レポヌトの準備に関するコヌチングを含む玠敵なパンがボヌナスずしお送られたす。

私たちの䌚議は、志を同じくする人々、産業゚ンゞンずの出䌚いのためのプラットフォヌムであり、Python開発者が愛するものバック゚ンドずりェブ、デヌタ収集ず凊理、AI / ML、テスト、IoTなどに぀いお話し合い、議論するためのプラットフォヌムです。 秋の様子は、 Pythonチャンネルの ビデオレポヌトをご芧になり、チャンネルに登録しおください。近日䞭にカンファレンスからの最高のレポヌトを無料でアクセスできるように投皿したす。

Source: https://habr.com/ru/post/J433474/


All Articles