1657011954
1657011234
1657010870
#discord #python #bot #AI #machine-learning #code
1657010758
#Python #programming #code #bot #artificial-intelligence #machine-learning
1656827424
ethereum_addresses
import "package:convert/convert.dart" show hex;
import "package:ethereum_addresses/ethereum_addresses.dart";
final publicKey = hex.decode(
"028a8c59fa27d1e0f1643081ff80c3cf0392902acbf76ab0dc9c414b8d115b0ab3",
);
// Derives an Ethereum address from a given public key.
ethereumAddressFromPublicKey(Uint8List.fromList(hex.decode(publicKey)),);
// => "0xD11A13f484E2f2bD22d93c3C3131f61c05E876a9"
// Converts an Ethereum address to a checksummed address (EIP-55).
checksumEthereumAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed");
// => "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
// Returns whether a given Ethereum address is valid.
isValidEthereumAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed");
// => true
isValidEthereumAddress("0x5aaeb6053F3E94C9b9A09f33669435E7Ef1beaed");
// => false
MIT License
Copyright (c) 2019 Peter Jihoon Kim
Run this command:
With Dart:
$ dart pub add ethereum_addresses
With Flutter:
$ flutter pub add ethereum_addresses
This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get
):
dependencies:
ethereum_addresses: ^1.0.2
Alternatively, your editor might support dart pub get
or flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'dart:typed_data';
import "package:convert/convert.dart" show hex;
import "package:ethereum_addresses/ethereum_addresses.dart";
void main() {
final publicKey = hex.decode(
"028a8c59fa27d1e0f1643081ff80c3cf0392902acbf76ab0dc9c414b8d115b0ab3",
);
// Derives an Ethereum address from a given public key.
print(ethereumAddressFromPublicKey(Uint8List.fromList(publicKey)));
// Converts an Ethereum address to a checksummed address (EIP-55).
print(checksumEthereumAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"));
// Returns whether a given Ethereum address is valid.
print(isValidEthereumAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"));
print(isValidEthereumAddress("0x5aaeb6053F3E94C9b9A09f33669435E7Ef1beaed"));
}
Download Details:
Author: fuseio
Source Code: https://github.com/fuseio/dart-ethereum_address
1656822600
BARE Code generator
The code generator transforms all *.bare files into
It produces the necessary classes and the extension methods.
After running the code generator with
$ dart run build_runner build
To encode a class
<class_name>.toBare
To decode to a class
<class_name>.fromBare(bytes)
See the example folder for a sample of a schema file and the generated code.
to_string
- Generate toString
override for the generated classes. Default - True
targets:
$default:
builders:
bare_codegen|bareGenerator:
options:
to_string: True
Update your dependencies:
$ cd bare && pub get
$ cd bare_codegen && pub get
$ cd example && pub get
cd
into example
and run build process and run tests:
$ dart run build_runner build
$ dart test
Run this command:
With Dart:
$ dart pub add bare_codegen
With Flutter:
$ flutter pub add bare_codegen
This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get
):
dependencies:
bare_codegen: ^0.1.2
Alternatively, your editor might support dart pub get
or flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:bare_codegen/bare_codegen.dart';
Author: Kaashyapan
Source Code: https://github.com/kaashyapan/bare-dart
License: MIT
1655811120
¡Resolver conflictos de fusión es divertido! - nunca dijo nadie. A nadie le gustan, pero eso no cambia el hecho de que son, bueno, parte del trabajo. En esta breve publicación, me gustaría explicar brevemente de dónde vienen, mostrar cómo resolverlos y, como beneficio adicional, ¡sugerir una solución para evitarlos potencialmente en primer lugar!
Un conflicto de combinación es un evento que ocurre cuando Git no puede resolver automáticamente las diferencias en el código entre dos confirmaciones. Pueden ocurrir como resultado de la fusión de ramas, durante una reorganización o cuando estás seleccionando en Git.
La buena noticia es que Git es bastante bueno para descifrar cómo integrar cambios, por lo que la mayor parte del tiempo está a salvo. La parte difícil comienza cuando se modifica exactamente la misma línea y Git no puede decidir qué versión es la correcta. Te lo notificará en la terminal y te hará responsable de arreglarlo.
Bien, entonces, ¿cómo resolvemos los conflictos? Podemos resolverlos manualmente o usar una GUI de Git como las integradas en VS Code o una aplicación de escritorio separada como Tower o Sourcetree .
Veamos cómo resolver conflictos manualmente.
Los cambios en conflicto son fáciles de detectar, ya que se marcarán en un archivo con <<<<<<<
y >>>>>>>
. La parte anterior =======
es su versión de las líneas en conflicto y la parte posterior es su versión. Elimine las líneas que no desee (incluidos los marcadores de conflicto) y conserve las que sean correctas.
Los conflictos de fusión no dan tanto miedo como parecen, pero la verdad es que resolverlos puede ser un proceso largo y tedioso, especialmente en proyectos grandes.
Ahí es donde GitLive es útil: una poderosa herramienta que le avisará dentro de su IDE en el momento en que realice un cambio en el editor que entre en conflicto con un cambio en cualquier otra rama. Las advertencias le muestran conflictos potenciales y le brindan la oportunidad de resolverlos antes de que ocurra el conflicto de combinación.
Instale la última extensión de GitLive VS Code para comenzar.
Cuando abre un archivo, verá los indicadores de cambio en el margen de su editor que le muestra dónde sus compañeros de equipo han realizado cambios en comparación con su versión del archivo. Estos se actualizan en tiempo real a medida que usted y sus compañeros de equipo están editando.
Si ha realizado un cambio conflictivo, verá el indicador de conflicto de color rojo brillante. Estos conflictos pueden ser cambios locales no confirmados que aún no ha enviado o cambios existentes en su rama que entran en conflicto con los cambios de sus compañeros de equipo.
Pase el cursor sobre las líneas afectadas en VS Code para inspeccionar un cambio y ver la diferencia, de qué rama son e incluso seleccionar cambios directamente en su archivo local.
Eso es todo, espero que encuentre útil mi miniguía sobre cómo lidiar con los conflictos de fusión y la próxima vez que encuentre un conflicto de fusión no entre en pánico. ¡Déjame saber lo que piensas en los comentarios!
Esta historia se publicó originalmente en https://www.c-sharpcorner.com/article/can-you-resolve-merge-conflicts-before-they-happen/
1655810700
マージの競合を解決するのは楽しいです! -誰も言ったことがない。誰もそれらを好きではありませんが、それは彼らが仕事の一部であるという事実を変えることはありません。この短い投稿では、それらがどこから来たのかを簡単に説明し、それらを解決する方法を示し、追加のボーナスとして、そもそもそれらを回避する可能性のある解決策を提案したいと思います!
マージの競合は、Gitが2つのコミット間のコードの違いを自動的に解決できない場合に発生するイベントです。これらは、ブランチのマージの結果として、リベース中、またはGitでチェリーピッキングをしているときに発生する可能性があります。
幸いなことに、Gitは変更を統合する方法を理解するのに非常に優れているため、ほとんどの場合、安全です。難しい部分は、まったく同じ行が変更され、Gitがどちらのバージョンが正しいかを判断できないときに始まります。それはターミナルでそれについてあなたに通知し、あなたにそれを修正する責任を負わせます。
では、どうすれば競合を解決できますか?それらを手動で解決するか、VSCodeに組み込まれているような GitGUI、またはTower や Sourcetreeなどの別のデスクトップアプリケーションを使用することができます。
競合を手動で解決する方法を見てみましょう。
<<<<<<<
競合する変更は、ファイル内でと によってマークされるため、簡単に見つけることができます >>>>>>>
。前の部分 =======
は競合する行のバージョンであり、後の部分はそれらのバージョンです。不要な行(競合マーカーを含む)を削除し、正しい行を保持します。
競合をマージすることは、見た目ほど恐ろしいことではありませんが、真実は、特に大規模なプロジェクトでは、競合を解決するのに長くて退屈なプロセスになる可能性があるということです。
そこでGitLiveが便利になります。これは、他のブランチの変更と競合するエディターで変更を加えたときにIDE内で警告する強力なツールです。警告は、潜在的な競合を示し、マージの競合が発生する前にそれらを解決する機会を提供します。
開始するには、最新のGitLiveVSCode 拡張機能をインストールして ください。
ファイルを開くと、エディターのガターに変更インジケーターが表示され、ファイルのバージョンと比較してチームメートが変更を加えた場所が示されます。これらは、あなたとあなたのチームメートが編集しているときにリアルタイムで更新されます。
競合する変更を行った場合は、明るい赤色の競合インジケーターが表示されます。これらの競合は、まだプッシュしていないコミットされていないローカルの変更、またはチームメートの変更と競合するブランチ上の既存の変更である可能性があります。
VS Codeで影響を受けた行をロールオーバーして、変更を調べ、差分、それらがどのブランチからのものであるか、さらにはチェリーピックの変更をローカルファイルに直接確認します。
それだけです。マージの競合に対処する方法についての私のミニガイドが役立つことを願っています。次にマージの競合が発生したときに、慌てる必要はありません。コメントであなたの考えを教えてください!
このストーリーは、もともとhttps://www.c-sharpcorner.com/article/can-you-resolve-merge-conflicts-before-they-happen/で公開されました。
1655258400
最近、大規模なReactアプリのコードベースを使用しているときに、コンパイル時または実行時のエラーではなく、予期しないコードの動作である3つのカテゴリのバグに対して急停止しました。
もちろん、私たちの最初の本能は、「悪を見つけることができるところならどこでも悪と戦うこと」でした。
しかし、一連の印刷ステートメントの後でも、バグを追跡するのは困難なままでした。そのとき、コードの特定の部分がアンチパターンと見なされる可能性があることに気づきました。
そのため、将来これらの間違いを確実に回避するために、それらを理解し、そのように特徴づけることに多くの時間を費やしました。
この記事はそれらの発見を説明する試みです。
Reactのパターンとアンチパターン
この記事では、Reactコードは、次の場合に適切なパターンと見なされます。
上記の目的を達成するために、より多くのコード行を記述した場合、または(予想どおり)いくつかの追加のレンダリングを導入した場合でも、コードはパターンと見なされることに注意してください。
アンチパターンを識別する方法は?
Reactでは、さまざまなコードが依存関係によって相互にリンクされています。これらの相互依存するコードは、アプリケーションの状態を目的の形式に保ちます。したがって、依存関係のないコードをReactで作成すると、バグが発生する可能性が高くなります。
したがって、などのフックを使用する場合は、依存関係の配列を使用しないため、注意が必要useState
ですuseRef
。
ソース:imgflip.com
Reactコンポーネントを配置するメカニズムは2つあります。
著者による画像
「Child3」にバグがあることがわかったシナリオを想像してみましょう。
コンポジションを使用してコンポーネントを配置した場合、すべてが独立しているため、「子1」と「子2」のコードを調べる必要はありません。したがって、デバッグの時間計算量はになりますO (1)
。
ただし、ネストを使用してコンポーネントを配置した場合は、「子3」の前にすべての子をチェックして、バグの原因を特定する必要があります。この場合、デバッグの時間計算量は、「子3」を超える子の数はO (n)
どこになりますか。n
したがって、ネストすると、構成よりもデバッグプロセスが困難になることが多いと結論付けることができます。
サンプルアプリ
それでは、さまざまなパターンとアンチパターンを示すアプリについて考えてみましょう。
左側のナビゲーションメニューで記事をクリックすると、右側に開きます。これに続いて2つのアクションがあります。
(num_chars(title) + num_chars(text)
、表示されます。このアプリを構築する正しい方法は、次の4つのステップで説明します。
右下のボタンをクリックすると、上のサンドボックスを開くことができます。
ディレクトリには、src/pages
各ステップにマップされたページがあります。の各ページのファイルには、コンポーネントがsrc/pages
含まれています。ArticleContent
議論中のコードはこのArticleContent
コンポーネントの中にあります。
ここで、上記の4つのアプローチで採用されたアンチパターンとパターンを確認しましょう。
間違ったアプローチでは、props
またはcontext
の初期値として使用されていuseState
ますuseRef
。27行目では、平均の長さが計算され、状態として保存されていることがわかります。
import { useCallback, useEffect, useState } from "react";
import { useGetArticles } from "../hooks/useGetArticles";
import { useGetEmoji } from "../hooks/useGetEmoji";
import { Articles } from "../types";
import { Navigation } from "../components/Navigation";
const styles: { [key: string]: React.CSSProperties } = {
container: {
background: "#FEE2E2",
height: "100%",
display: "grid",
gridTemplateColumns: "10rem auto"
},
content: {}
};
const ArticleContent: React.FC<{
article: Articles["articles"]["0"];
}> = (props) => {
// Step 1. calculate length as we need it to get corresponding emotion
const [length] = useState<number>(
props.article.text.length + props.article.title.length
);
// Step 2. fetch emotion map from backend
const emotions = useGetEmoji();
// Step 3. set emotion once we get emotion map from backend
const [emotion, setEmotion] = useState<string>("");
useEffect(() => {
if (emotions) {
setEmotion(emotions["stickers"][length]);
}
}, [emotions, length]);
return (
<div>
<div>
<h2>{props.article.title}</h2>
<div>{props.article.text}</div>
</div>
<h3
dangerouslySetInnerHTML={{
__html: `Total Length ${length} ${emotion}`
}}
/>
</div>
);
};
const Incorrect: React.FC = () => {
const articles = useGetArticles();
const [currentArticle, setCurrentArticle] = useState<
Articles["articles"]["0"] | null
>();
const onClickHandler = useCallback((article) => {
setCurrentArticle(article);
}, []);
return (
<div style={styles.container}>
<Navigation articles={articles} onClickHandler={onClickHandler} />
<div style={styles.content}>
{currentArticle ? <ArticleContent article={currentArticle} /> : null}
</div>
</div>
);
};
export default Incorrect;
このアンチパターンが、新しい記事が選択されたときに計算もネットワーク要求もトリガーされない理由です。
「DestroyandRecreate」アンチパターンを使用して、誤ったアプローチを修正しましょう。
機能コンポーネントを破棄することは、最初の関数呼び出し中に作成されたすべてのフックと状態を破棄することを意味します。再作成とは、以前に呼び出されたことがないかのように関数を再度呼び出すことを指します。
親コンポーネントは、key
小道具を使用してコンポーネントを破棄し、変更するたびに再作成できることに注意してくださいkey
。はい、あなたはそれを正しく読んでいます—あなたはループの外でキーを使うことができます。
具体的には、ファイル内の親コンポーネントkey
の子コンポーネントをレンダリングしながら、propを使用して「Destroyand Recreate」アンチパターンを実装します(89行目)。ArticleContentRecreateDocRecreateDoc.tsx
import { useCallback, useEffect, useState } from "react";
import { Navigation } from "../components/Navigation";
import { useGetArticles } from "../hooks/useGetArticles";
import { useGetEmoji } from "../hooks/useGetEmoji";
import { Articles } from "../types";
const styles: { [key: string]: React.CSSProperties } = {
container: {
background: "#FEFCE8",
height: "100%",
display: "grid",
gridTemplateColumns: "10rem auto"
},
content: {}
};
const ArticleContent: React.FC<{
article: Articles["articles"]["0"];
}> = (props) => {
// Step 2. fetch emotion map from backend
const emotions = useGetEmoji();
// Step 3, set emotion once we get emotion map from backend
const [emotion, setEmotion] = useState<string>("");
useEffect(() => {
if (emotions) {
setEmotion(
emotions["stickers"][
props.article.text.length + props.article.title.length
]
);
}
}, [emotions, props]);
return (
<div>
<div>
<h2>{props.article.title}</h2>
<div>{props.article.text}</div>
</div>
<h3
dangerouslySetInnerHTML={{
__html: `Total Length ${
props.article.text.length + props.article.title.length
} ${emotion}`
}}
/>
</div>
);
};
const PartiallyCorrect: React.FC = () => {
const articles = useGetArticles();
const [currentArticle, setCurrentArticle] = useState<
Articles["articles"]["0"] | null
>();
const onClickHandler = useCallback((article) => {
setCurrentArticle(article);
}, []);
return (
<div style={styles.container}>
<Navigation articles={articles} onClickHandler={onClickHandler} />
<div style={styles.content}>
{currentArticle ? <ArticleContent article={currentArticle} /> : null}
</div>
</div>
);
};
export default PartiallyCorrect;
アプリは期待どおりに動作しますが、新しい記事を選択するとちらつき効果が見られます。したがって、このアンチパターンは部分的に正しい出力になります。
「DestroyandRecreate」アンチパターンを使用する代わりに、この「正しいが最適ではない」アプローチでは、「rerendering」を使用します。
レンダリングとは、関数呼び出し全体でフックをそのままにして、react関数コンポーネントを再度呼び出すことを指します。「破棄して再作成」では、すべてのフックが最初に破棄されてから、最初から再作成されることに注意してください。
'rerendering'を実装useEffect
するuseState
には、タンデムで使用されます。の初期値はまたはuseState
に設定でき、実行後に実際の値が計算されて割り当てられます。このパターンでは、を使用して依存関係配列の欠如を回避しています。nullundefineduseEffectuseStateuseEffect
具体的には、合計文字数の計算をJSX(50行目)に移動し、 (31行目)props
の依存関係として(41行目)を使用していることに注目してください。useEffect
import { useCallback, useEffect, useState } from "react";
import { Navigation } from "../components/Navigation";
import { useGetArticles } from "../hooks/useGetArticles";
import { useGetEmoji } from "../hooks/useGetEmoji";
import { Articles } from "../types";
const styles: { [key: string]: React.CSSProperties } = {
container: {
background: "#FEF2F2",
height: "100%",
display: "grid",
gridTemplateColumns: "10rem auto"
},
content: {}
};
const ArticleContent: React.FC<{
article: Articles["articles"]["0"];
}> = (props) => {
// Step 1. calculate length as we need it to get corresponding emotion
const [length] = useState<number>(
props.article.text.length + props.article.title.length
);
// Step 2. fetch emotion map from backend
const emotions = useGetEmoji();
// Step 3. set emotion once we get emotion map from backend
const [emotion, setEmotion] = useState<string>("");
useEffect(() => {
if (emotions) {
setEmotion(emotions["stickers"][length]);
}
}, [emotions, length]);
return (
<div>
<div>
<h2>{props.article.title}</h2>
<div>{props.article.text}</div>
</div>
<h3
dangerouslySetInnerHTML={{
__html: `Total Length ${length} ${emotion}`
}}
/>
</div>
);
};
const Suboptimal: React.FC = () => {
const articles = useGetArticles();
const [currentArticle, setCurrentArticle] = useState<
Articles["articles"]["0"] | null
>();
const onClickHandler = useCallback((article) => {
setCurrentArticle(article);
}, []);
return (
<div style={styles.container}>
<Navigation articles={articles} onClickHandler={onClickHandler} />
<div style={styles.content}>
{/** Step 4. Using key to force destroy and recreate */}
{currentArticle ? (
<ArticleContent article={currentArticle} key={currentArticle.id} />
) : null}
</div>
</div>
);
};
export default Suboptimal;
このパターンを使用すると、ちらつきの影響は回避されますが、小道具が変更されるたびに絵文字をフェッチするようにネットワーク要求が行われます。そのため、文字数に変更がない場合でも、同じ絵文字を取得するように不要なリクエストが行われます。
今回は正しく、そして最適にやってみましょう。それはすべてアンチパターン#1から、props
またはcontext
初期状態として始まりました。
props
の依存関係として使用することで、これを修正できますuseMemo
。合計文字数の計算をuseMemo
フックに移動することで、平均の長さが変更されない限り、ネットワーク要求が絵文字をフェッチするのを防ぐことができます。
import { useCallback, useEffect, useMemo, useState } from "react";
import { Navigation } from "../components/Navigation";
import { useGetArticles } from "../hooks/useGetArticles";
import { useGetEmoji } from "../hooks/useGetEmoji";
import { Articles } from "../types";
const styles: { [key: string]: React.CSSProperties } = {
container: {
background: "#F0FDF4",
height: "100%",
display: "grid",
gridTemplateColumns: "10rem auto"
},
content: {}
};
const ArticleContent: React.FC<{
article: Articles["articles"]["0"];
}> = (props) => {
// Step 1. calculate length as we need it to get corresponding emotion
const length = useMemo<number>(
() => props.article.text.length + props.article.title.length,
[props]
);
// Step 2. fetch emotion map from backend
const emotions = useGetEmoji();
// Step 3. set emotion once we get emotion map from backend
const [emotion, setEmotion] = useState<string>("");
useEffect(() => {
if (emotions) {
setEmotion(emotions["stickers"][length]);
}
}, [emotions, length]);
return (
<div>
<div>
<h2>{props.article.title}</h2>
<div>{props.article.text}</div>
</div>
<h3
dangerouslySetInnerHTML={{
__html: `Total Length ${length} ${emotion}`
}}
/>
</div>
);
};
const Optimal: React.FC = () => {
const articles = useGetArticles();
const [currentArticle, setCurrentArticle] = useState<
Articles["articles"]["0"] | null
>();
const onClickHandler = useCallback((article) => {
setCurrentArticle(article);
}, []);
return (
<div style={styles.container}>
<Navigation articles={articles} onClickHandler={onClickHandler} />
<div style={styles.content}>
{currentArticle ? <ArticleContent article={currentArticle} /> : null}
</div>
</div>
);
};
export default Optimal;
結論
この記事では、props
またはcontext
を初期状態として使用し、「破棄して再作成」をアンチパターンとして使用し、JSXで内部状態を使用props
し、依存関係として使用することuseMemo
は適切なパターンであることを説明しました。また、依存関係の配列なしでフックを使用し、Reactコンポーネントを配置するためにネストする場合は、注意が必要であることも学びました。
このストーリーは、もともとhttps://betterprogramming.pub/how-we-reduced-bugs-in-our-react-code-base-9a7a979b4442で公開されました
1655258400
Recientemente, mientras trabajábamos con nuestra gran base de código de la aplicación React, nos detuvimos en seco contra tres categorías de errores: no eran errores de tiempo de compilación o tiempo de ejecución, sino comportamientos de código inesperados.
Nuestro primer instinto fue, por supuesto, "combatir el mal dondequiera que pudiéramos encontrarlo".
Sin embargo, incluso después de una letanía de declaraciones impresas, los errores seguían siendo difíciles de rastrear. Fue entonces cuando nos dimos cuenta de que ciertas partes de nuestro código podrían considerarse antipatrones.
Así que pasamos mucho tiempo entendiéndolos y caracterizándolos como tales para asegurarnos de evitar estos errores en el futuro.
Este artículo es un intento de explicar esos descubrimientos.
Patrones y Antipatrones en React
En este artículo, un código React califica como un buen patrón si:
Tenga en cuenta que el código todavía se considera un patrón si escribimos más líneas de código o (como era de esperar) introdujimos algunos renderizados adicionales para lograr los objetivos anteriores.
¿Cómo identificar los antipatrones?
En React, diferentes piezas de código están vinculadas entre sí por dependencias. Estas piezas de código interdependiente juntas mantienen el estado de la aplicación en su forma deseada. Por lo tanto, si escribimos un fragmento de código en React que no tiene una dependencia, existe una alta probabilidad de que genere errores.
Por lo tanto, tenga cuidado cuando utilice ganchos como useState
, useRef
etc. porque no toman matrices de dependencias.
Fuente: imgflip.com
Hay dos mecanismos mediante los cuales se organizan los componentes de React:
Imagen por autor
Imaginemos un escenario donde observamos que hay un error en “Niño 3”.
Si hubiéramos arreglado los componentes usando composición, no tendríamos que buscar en el código de “Niño 1” y “Niño 2” porque todos son independientes. Por lo tanto, la complejidad temporal de la depuración sería O (1)
.
Sin embargo, si hubiéramos arreglado los componentes mediante el anidamiento, tendríamos que verificar todos los elementos secundarios antes del "Niño 3" para descubrir el origen del error. En este caso, la complejidad temporal de la depuración sería O (n)
dónde n
está el número de hijos por encima de "Niño 3".
Por lo tanto, podemos concluir que el anidamiento a menudo hace que el proceso de depuración sea más difícil que la composición.
Aplicación de ejemplo
Ahora, consideremos una aplicación para demostrar diferentes patrones y antipatrones.
Cuando se hace clic en un artículo en el menú de navegación izquierdo, se abre a la derecha. A esto le siguen dos acciones:
(num_chars(title) + num_chars(text)
y se muestra.Llegaremos a la forma correcta de construir esta aplicación en cuatro pasos:
Puede abrir el sandbox anterior haciendo clic en el botón en la esquina inferior derecha.
El src/pages
directorio tiene páginas asignadas a cada paso. El archivo de cada página src/pages
contiene un ArticleContent
componente. El código en discusión está dentro de este ArticleContent
componente.
Repasemos ahora los antipatrones y patrones seguidos en los cuatro enfoques anteriores.
En el enfoque incorrecto, props
o context
se ha utilizado como valor inicial para useState
o useRef
. En la línea 27, podemos ver que la longitud promedio ha sido calculada y almacenada como un estado.
import { useCallback, useEffect, useState } from "react";
import { useGetArticles } from "../hooks/useGetArticles";
import { useGetEmoji } from "../hooks/useGetEmoji";
import { Articles } from "../types";
import { Navigation } from "../components/Navigation";
const styles: { [key: string]: React.CSSProperties } = {
container: {
background: "#FEE2E2",
height: "100%",
display: "grid",
gridTemplateColumns: "10rem auto"
},
content: {}
};
const ArticleContent: React.FC<{
article: Articles["articles"]["0"];
}> = (props) => {
// Step 1. calculate length as we need it to get corresponding emotion
const [length] = useState<number>(
props.article.text.length + props.article.title.length
);
// Step 2. fetch emotion map from backend
const emotions = useGetEmoji();
// Step 3. set emotion once we get emotion map from backend
const [emotion, setEmotion] = useState<string>("");
useEffect(() => {
if (emotions) {
setEmotion(emotions["stickers"][length]);
}
}, [emotions, length]);
return (
<div>
<div>
<h2>{props.article.title}</h2>
<div>{props.article.text}</div>
</div>
<h3
dangerouslySetInnerHTML={{
__html: `Total Length ${length} ${emotion}`
}}
/>
</div>
);
};
const Incorrect: React.FC = () => {
const articles = useGetArticles();
const [currentArticle, setCurrentArticle] = useState<
Articles["articles"]["0"] | null
>();
const onClickHandler = useCallback((article) => {
setCurrentArticle(article);
}, []);
return (
<div style={styles.container}>
<Navigation articles={articles} onClickHandler={onClickHandler} />
<div style={styles.content}>
{currentArticle ? <ArticleContent article={currentArticle} /> : null}
</div>
</div>
);
};
export default Incorrect;
Este antipatrón es la razón por la que ni el cálculo ni la solicitud de red se activan cuando se selecciona un nuevo artículo.
Enmiendemos nuestro enfoque incorrecto utilizando el antipatrón 'Destruir y recrear'.
Destruir un componente funcional se refiere a destruir todos los ganchos y los estados creados durante la primera llamada a la función. Recrear se refiere a volver a llamar a la función como si nunca antes se hubiera llamado.
Tenga en cuenta que un componente principal puede usar el key
accesorio para destruir el componente y volver a crearlo cada vez que key
cambia. Sí, lo leíste bien: puedes usar teclas fuera de los bucles.
Específicamente, implementamos el antipatrón 'Destruir y recrear' usando el key
accesorio mientras renderizamos el componente secundario ArticleContent
del componente principal RecreateDoc
en el RecreateDoc.tsx
archivo (línea 89).
import { useCallback, useEffect, useState } from "react";
import { Navigation } from "../components/Navigation";
import { useGetArticles } from "../hooks/useGetArticles";
import { useGetEmoji } from "../hooks/useGetEmoji";
import { Articles } from "../types";
const styles: { [key: string]: React.CSSProperties } = {
container: {
background: "#FEFCE8",
height: "100%",
display: "grid",
gridTemplateColumns: "10rem auto"
},
content: {}
};
const ArticleContent: React.FC<{
article: Articles["articles"]["0"];
}> = (props) => {
// Step 2. fetch emotion map from backend
const emotions = useGetEmoji();
// Step 3, set emotion once we get emotion map from backend
const [emotion, setEmotion] = useState<string>("");
useEffect(() => {
if (emotions) {
setEmotion(
emotions["stickers"][
props.article.text.length + props.article.title.length
]
);
}
}, [emotions, props]);
return (
<div>
<div>
<h2>{props.article.title}</h2>
<div>{props.article.text}</div>
</div>
<h3
dangerouslySetInnerHTML={{
__html: `Total Length ${
props.article.text.length + props.article.title.length
} ${emotion}`
}}
/>
</div>
);
};
const PartiallyCorrect: React.FC = () => {
const articles = useGetArticles();
const [currentArticle, setCurrentArticle] = useState<
Articles["articles"]["0"] | null
>();
const onClickHandler = useCallback((article) => {
setCurrentArticle(article);
}, []);
return (
<div style={styles.container}>
<Navigation articles={articles} onClickHandler={onClickHandler} />
<div style={styles.content}>
{currentArticle ? <ArticleContent article={currentArticle} /> : null}
</div>
</div>
);
};
export default PartiallyCorrect;
La aplicación funciona como se esperaba, pero se observa un efecto de parpadeo cuando se selecciona un nuevo artículo. Por lo tanto, este antipatrón da como resultado una salida parcialmente correcta.
En lugar de usar el antipatrón 'Destruir y recrear', en este enfoque 'correcto pero subóptimo', usaremos 'reprocesar'.
Volver a renderizar se refiere a volver a llamar al componente funcional de reacción con los ganchos intactos en todas las llamadas a funciones. Tenga en cuenta que en 'Destruir y recrear', todos los ganchos se destruyen primero y luego se recrean desde cero.
Para implementar 'renderizado', useEffect
y useState
se usará en tándem. El valor inicial de useState
puede establecerse en null
o undefined
y se calculará y asignará un valor real una vez que se useEffect
haya ejecutado. En este patrón, estamos eludiendo la falta de matriz de dependencia useState
mediante el uso de useEffect
.
Específicamente, observe cómo hemos movido el cálculo del recuento total de caracteres a JSX (línea 50) y estamos usando props
(línea 41) como una dependencia en useEffect
(línea 31).
import { useCallback, useEffect, useState } from "react";
import { Navigation } from "../components/Navigation";
import { useGetArticles } from "../hooks/useGetArticles";
import { useGetEmoji } from "../hooks/useGetEmoji";
import { Articles } from "../types";
const styles: { [key: string]: React.CSSProperties } = {
container: {
background: "#FEF2F2",
height: "100%",
display: "grid",
gridTemplateColumns: "10rem auto"
},
content: {}
};
const ArticleContent: React.FC<{
article: Articles["articles"]["0"];
}> = (props) => {
// Step 1. calculate length as we need it to get corresponding emotion
const [length] = useState<number>(
props.article.text.length + props.article.title.length
);
// Step 2. fetch emotion map from backend
const emotions = useGetEmoji();
// Step 3. set emotion once we get emotion map from backend
const [emotion, setEmotion] = useState<string>("");
useEffect(() => {
if (emotions) {
setEmotion(emotions["stickers"][length]);
}
}, [emotions, length]);
return (
<div>
<div>
<h2>{props.article.title}</h2>
<div>{props.article.text}</div>
</div>
<h3
dangerouslySetInnerHTML={{
__html: `Total Length ${length} ${emotion}`
}}
/>
</div>
);
};
const Suboptimal: React.FC = () => {
const articles = useGetArticles();
const [currentArticle, setCurrentArticle] = useState<
Articles["articles"]["0"] | null
>();
const onClickHandler = useCallback((article) => {
setCurrentArticle(article);
}, []);
return (
<div style={styles.container}>
<Navigation articles={articles} onClickHandler={onClickHandler} />
<div style={styles.content}>
{/** Step 4. Using key to force destroy and recreate */}
{currentArticle ? (
<ArticleContent article={currentArticle} key={currentArticle.id} />
) : null}
</div>
</div>
);
};
export default Suboptimal;
Con este patrón, se ha evitado el efecto de parpadeo, pero se realiza una solicitud de red para obtener emojis cada vez que cambian los accesorios. Por lo tanto, incluso si no hay cambios en el número de caracteres, se realiza una solicitud innecesaria para obtener el mismo emoji.
Hagámoslo correctamente y de manera óptima esta vez. Todo comenzó con Anti-Pattern #1: props
o context
como estado inicial.
Podemos arreglar esto usando props
como una dependencia en useMemo
. Al mover el cálculo del recuento total de caracteres al useMemo
enlace, podemos evitar que las solicitudes de red obtengan emojis a menos que la longitud promedio haya cambiado.
import { useCallback, useEffect, useMemo, useState } from "react";
import { Navigation } from "../components/Navigation";
import { useGetArticles } from "../hooks/useGetArticles";
import { useGetEmoji } from "../hooks/useGetEmoji";
import { Articles } from "../types";
const styles: { [key: string]: React.CSSProperties } = {
container: {
background: "#F0FDF4",
height: "100%",
display: "grid",
gridTemplateColumns: "10rem auto"
},
content: {}
};
const ArticleContent: React.FC<{
article: Articles["articles"]["0"];
}> = (props) => {
// Step 1. calculate length as we need it to get corresponding emotion
const length = useMemo<number>(
() => props.article.text.length + props.article.title.length,
[props]
);
// Step 2. fetch emotion map from backend
const emotions = useGetEmoji();
// Step 3. set emotion once we get emotion map from backend
const [emotion, setEmotion] = useState<string>("");
useEffect(() => {
if (emotions) {
setEmotion(emotions["stickers"][length]);
}
}, [emotions, length]);
return (
<div>
<div>
<h2>{props.article.title}</h2>
<div>{props.article.text}</div>
</div>
<h3
dangerouslySetInnerHTML={{
__html: `Total Length ${length} ${emotion}`
}}
/>
</div>
);
};
const Optimal: React.FC = () => {
const articles = useGetArticles();
const [currentArticle, setCurrentArticle] = useState<
Articles["articles"]["0"] | null
>();
const onClickHandler = useCallback((article) => {
setCurrentArticle(article);
}, []);
return (
<div style={styles.container}>
<Navigation articles={articles} onClickHandler={onClickHandler} />
<div style={styles.content}>
{currentArticle ? <ArticleContent article={currentArticle} /> : null}
</div>
</div>
);
};
export default Optimal;
Conclusión
En este artículo, discutimos que usar props
o context
como estado inicial y 'Destruir y recrear' son antipatrones mientras que usar el estado interno en JSX y props
como una dependencia useMemo
son buenos patrones. También aprendimos que debemos ser cautelosos cuando usamos ganchos sin una matriz de dependencia y anidamos para organizar los componentes de React.
Esta historia se publicó originalmente en https://betterprogramming.pub/how-we-reduced-bugs-in-our-react-code-base-9a7a979b4442
1654881540
La complejidad temporal de iterar sobre una matriz es O(n). Suena aceptable, ¿verdad? Podríamos descuidar su impacto en el rendimiento de nuestro código y no esforzarnos más en pensar en una mejor solución cuando tenemos muchos trabajos que hacer antes de la fecha límite. Pero una vez que usar esta habilidad se convierte en una respuesta natural, es posible que no nos demos cuenta de que incluso hemos escrito bucles for anidados o filtros anidados en nuestro código, lo que aumenta la complejidad del tiempo a O(n²), y es muy probable que suframos problemas de rendimiento. problemas más tarde.
Intente buscar en tipos de colección no secuenciales, como Set
y Dictionary
, en lugar de Array
, ya que la complejidad temporal de buscar en ay a Set
es dictionary
O(1), mientras que la complejidad temporal de buscar en an Array
es O(n).
En este artículo, le mostraré cómo optimizar la velocidad con Set
y dictionary
.
Ejemplo 1: Conjunto
En el ejemplo 1, están los datos de todos los miembros y la identificación de los miembros platino. El objetivo es derivar una matriz de miembros platino de members
with platinumMemberIDs
.
let members: [Member]
let platinumMemberIDs:[Int]
La complejidad temporal de filter(_:)
es O(n), y la complejidad temporal del método de instancia de la matriz contains(_:)
también es O(n).
El siguiente enfoque itera a través members
de array. En cada iteración, busca platinumMemberIDs
para verificar si platinumMemberIDs
contiene la identificación del miembro actual.
La complejidad temporal de este enfoque es O(n²).
var platinumMembers = members.filter {
platinumMemberIDs.contains($0.id)
}
Dado que el orden de platinumMemberIDs
no importa, podemos mantener estos datos en el tipo de recopilación no secuencial Set
. La complejidad temporal de la búsqueda en a Set
es O(1). La complejidad temporal total se reduce a O(n).
let platinumMemberIDSet = Set(platinumMemberIDs)var platinumMembers = members.filter {
platinumMemberIDSet.contains($0.id)
}
Ejemplo 2: Diccionario
En el ejemplo 2, hay dos grupos de usuarios que provienen de diferentes fuentes y nos gustaría conocer su intersección. Siempre que el correo electrónico de un usuario sea el mismo que el de otro usuario del otro grupo, suponemos que es el mismo usuario.
let starUsers: [User]
let recentUsers: [User]
La forma más sencilla es escribir bucles for anidados para averiguar los usuarios duplicados, pero la complejidad del tiempo será O(n²), no muy ideal.
var duplicatedUsers = [User]()for star in starUsers {
for recent in recentUsers {
if star.email == recent.email {
duplicatedUsers.append(star)
}
}
}
Para reducir la complejidad del tiempo, simplemente podemos crear un diccionario a partir de una recentUsers
matriz con el correo electrónico de cada usuario como clave y el objeto de usuario como valor.
let recentUserMap = Dictionary(uniqueKeysWithValues: recentUsers.map { ($0.email, $0) })
Y luego buscamos a través de la starUsers
matriz, pero en cada iteración, no necesitamos iterar sobre la recentUsers
matriz para encontrar los usuarios duplicados después de la optimización. En su lugar, accedemos al objeto de usuario de un usuario reciente cuyo correo electrónico es el mismo que el del usuario estrella actual directamente desde recentUserMap
. La complejidad del tiempo se reduce a O(n).
let duplicatedUsers = starUsers.filter {
guard let _ = recentUserMap[$0.email] else { return false }
return true
}
Esta historia se publicó originalmente en https://betterprogramming.pub/how-to-boost-your-ios-code-performance-reduce-searching-an-array-55fbdfee2050
1654828380
La revisión de código es un enfoque ampliamente adoptado en el mundo del desarrollo de software. Hacer que otros desarrolladores verifiquen su código (y que verifiquen el de ellos a su vez) ayuda a eliminar errores, limpiar la base de código y compartir conocimientos con todo el equipo. Pero a pesar de lo útil que es, la revisión del código aún puede ser bastante estresante y llevar mucho tiempo. En este artículo, me gustaría compartir algunos consejos e ideas sobre cómo hacer que el proceso de revisión de código sea lo más simple y sencillo posible, tanto para quienes revisan como para quienes preparan el código para su revisión.
Hay debates sobre la necesidad de solicitudes de incorporación de cambios y revisiones de código, y algunos desarrolladores sugieren la programación en parejas y la programación en masa como alternativas. ( Wikipedia : La programación mafiosa es un enfoque de desarrollo de software en el que todo el equipo trabaja en lo mismo, al mismo tiempo, en el mismo espacio y en la misma computadora) .
Si bien el objetivo de este artículo no es comparar estos enfoques, quiero resaltar algunos beneficios del proceso de revisión de código.
He encontrado diferentes enfoques para la revisión de código, según la solicitud de incorporación de cambios, la preferencia del revisor, el tiempo disponible, etc.
La revisión del código es un proceso de comunicación puramente humano. Y una buena manera de acercarse a los humanos (especialmente a aquellos con los que necesita trabajar) es con empatía. Esto incluye lo obvio: cortesía, amabilidad y respeto. Pero puede ir un paso más allá y recordar que cuando asigna revisores para revisar su código, está solicitando el tiempo y la energía de otra persona. Por lo tanto, es bueno facilitarles el proceso y hacerlo probablemente resulte en una revisión de código más eficiente. Entonces, teniendo en cuenta la empatía, analicemos algunos consejos prácticos que pueden hacer que sus revisores de código estén más felices.
En general, todos estos consejos se pueden aplicar a casi cualquier tipo de sistema de desarrollo y control de versiones (VCS). Este artículo constará de dos partes: la teoría y la práctica. Para el ejemplo práctico, crearé una aplicación Flutter usando GitHub como cliente Git y Codemagic como herramienta CI/CD.
Entonces abrió su administrador de tareas, seleccionó un ticket y lo movió a "Progreso". ¿Que sigue?
Crea una sucursal en la que trabajarás. Y llamar a esa sucursal... ¿qué?
El nombre de la rama es su primera oportunidad para dar contexto a su tarea. En caso de que su revisor decida verificar el código localmente, deberá encontrar esa rama entre muchas otras.
Hay varios buenos enfoques entre los que puede elegir, dependiendo de cómo administre el trabajo.
Enfoque 1:
Si está usando una herramienta de administración de tareas que le da a sus tareas un prefijo o un número (por ejemplo, Jira), también puede usar ese prefijo, especialmente para configurar algunos enlaces, como mover el ticket a "Revisión de código" cuando se abre un PR o "Para probar" cuando se fusiona el PR. Por ejemplo, si el nombre de la tarea es ABC-57: Add list with purchases
, un buen nombre para una sucursal sería ABC-57_purchases_list
.
Pero ¿por qué no solo ABC-57
?
Bueno, comparemos estas dos opciones.
Lista de sucursales A: | Lista de sucursales B: |
---|---|
ABC-57 | ABC-57_purchases_list |
ABC-64 | ABC-64_fix_cart_npe |
ABC-73 | ABC-73_migrate_2.8 |
¿En cuál de estas listas encontrará más rápido la sucursal requerida? El uso de una convención de nomenclatura que incluya una descripción también ayuda cuando cambia sus propias sucursales con regularidad, ya que no necesita recordar ni buscar el número de ticket.
Enfoque 2:
Use el mismo enfoque prefix
+ description
, pero en lugar de anteponer el nombre con el número de boleto, prefije con lo que es: feature
, bug_fix
, refactor
, etc. Es mejor usar este enfoque solo si no hay nombres de boletos disponibles. Aquí hay un par de ejemplos:
feature_purchases_list
obug_fix_cart_npe
Enfoque 3:
Cualquier otra estrategia de nomenclatura de ramas está bien, siempre que sea consistente y tenga una estructura lógica. Nombrar ramas al azar puede eventualmente conducir al caos. No solo es más difícil navegar entre ellos, sino también más difícil de mantener. ¿Qué ramas están rancias? ¿Puedes eliminarlos? ¿Hay trabajo no fusionado? ¿Sigue siendo relevante?
Una estrategia de revisión de código que puede usar es la revisión por confirmación, pero solo si el historial de confirmación lo hace posible. Aquí hay un artículo realmente excelente y detallado sobre cómo nombrar correctamente sus confirmaciones.
En resumen, aquí hay algunas pautas:
Update headline text color
o Remove unused imports
.Con solo leer el historial de confirmaciones de la solicitud de extracción, el revisor ya puede obtener una cierta comprensión de lo que revisará, incluso antes de ver una sola línea de código.
Si desea ir un paso más allá, recientemente encontré un enfoque interesante para las confirmaciones. El contexto de este enfoque es más amplio que solo nombrar compromisos. Puedes leer todo sobre esto aquí .
Pero la idea general es escribir un nombre de confirmación antes de comenzar a codificar. Esto lo ayudará a mantenerse enfocado en un paso a la vez, brindará más claridad sobre lo que debe hacer e implícitamente hará que sus confirmaciones sean atómicas. Creo que esta es una toma muy interesante y planeo intentarlo yo mismo.
Lo primero que debe tener en cuenta es que usted, a diferencia del revisor, está actualmente inmerso en el contexto de su tarea y su solución. Pero cuando el revisor abre tu solicitud de extracción, todo lo que tiene es lo que le das. No escribieron el código y no pasaron X horas o días trabajando en el problema. Por lo tanto, es importante brindar el mayor contexto posible para que la revisión sea más útil.
Descripción
Cuando hablo de crear una descripción, no me refiero a simplemente vincular el ticket de Jira al PR y terminar con él. Esto requeriría que el revisor cambie a Jira y lea la descripción, que podría tener más información de la necesaria para la revisión del código... y, sin embargo, no daría ninguna pista sobre cómo se implementa la solución.
Puede evitar esto proporcionando una breve descripción y respondiendo tres preguntas:
Gracias a esta descripción, el revisor sabrá qué buscar en el código y por qué se escribió de esa manera. Esto les permite proporcionar comentarios más relevantes.
Puede asegurarse de esto agregando una plantilla de solicitud de extracción en GitHub.
Imágenes y videos
Evaluar el código sabiendo cómo se ve el resultado real es mucho más fácil que intentar imaginarlo. Si su tarea está relacionada con cambios en la interfaz de usuario, agregar una imagen del resultado final (o un video, si es un flujo) beneficiará en gran medida la comprensión del código por parte del revisor. O, si su tarea implica una lógica complicada, puede adjuntar un diagrama de secuencia que explique el algoritmo.
Cuanto mayor sea la solicitud de incorporación de cambios, peor será la revisión del código. La revisión del código es mentalmente un proceso difícil: debe leer el código, descubrir qué está haciendo, comprender cómo lo está haciendo y buscar posibles problemas. Cuantas más líneas de código necesites tener en cuenta, mayor será la posibilidad de que pases algo por alto. Aunque en realidad no hay un número "dorado" de líneas, recomendaría ceñirse a menos de 500. Si hay más, divídalas en varias solicitudes de extracción.
No, no estoy hablando de la arquitectura aquí. Durante el desarrollo, usamos todo tipo de trucos: generar registros de impresión, codificar valores específicos, dejar TODO
s, etc. Para ahorrar algo de tiempo y mantener el enfoque del revisor en la función en cuestión, acostúmbrese a asegurarse de que su solicitud de extracción esté limpia antes de asignar revisores. ¿Qué quiero decir con mantenerlo limpio?
TODO
:Consejo profesional: la mayoría de esos problemas se pueden resolver con herramientas de análisis de código estático, aunque algunos de ellos requieren la implementación de reglas personalizadas, según cómo desee manejarlos.
Esto puede parecer algo menor porque la función está lista, el código es definitivo y puede dejar los conflictos de fusión para más adelante... Pero eso no siempre es cierto. Su rama puede haber divergido mucho de la rama base, o algunos cambios pueden chocar con su código de una manera que hace que sea imposible fusionarse sin refactorizar. Y esto requerirá otra revisión o se fusionará con problemas posiblemente pasados por alto.
No es necesario revisar los archivos generados por código, como los modelos JSON o los simulacros de pruebas unitarias. Por lo tanto, verlos explícitamente en una diferencia no agrega ningún beneficio y solo es molesto. Diferentes clientes de Git tienen diferentes enfoques para excluir dichos archivos de la revisión. Por ejemplo, en GitHub, puede agregar los patrones a.gitattributes
, y se colapsarán en la diferencia final.
Es una buena práctica tener habilitado el análisis de código estático. Los documentos de Dart describen sus ventajas de manera muy concisa:
El análisis estático le permite encontrar problemas antes de ejecutar una sola línea de código. Es una poderosa herramienta que se utiliza para prevenir errores y garantizar que el código se ajuste a las pautas de estilo.
Los nuevos proyectos de Dart y Flutter tienen reglas de lint habilitadas de forma predeterminada. Puede leer cómo habilitarlos en proyectos existentes aquí . Consulta las reglas de Dart aquí y de Flutter aquí . Puede crear sus propias reglas o deshabilitar algunas existentes.
Antes de asignar revisores, asegúrese de que su código no viole ninguna regla de lint. Puede asegurarse de esto con CI: bloquee la posibilidad de fusionarse hasta que no haya advertencias de pelusa.
Consejo profesional: puede ir más allá con herramientas como Dart Code Metrics .
Las pruebas unitarias ayudan a identificar problemas y detectar errores en las primeras etapas de desarrollo antes de que alguien más vea el código, y mucho menos lo pruebe. Además, aseguran que el comportamiento del código permanezca igual en caso de cambios en el código. Como beneficio adicional, también hacen que escriba un código más limpio y más desacoplado, lo que contribuye a la calidad general del código.
Puede configurar CI para ejecutar pruebas unitarias en cada solicitud de extracción y bloquear la fusión en caso de que algo falle. Y debe corregir cualquier prueba fallida antes de marcar la solicitud de extracción como lista para revisión.
Consejo profesional: habilite los informes de cobertura de código con herramientas como Codecov .
El uso de marcadores como hitos y etiquetas es más un consejo agradable y, según el tamaño del proyecto, se puede omitir. Pero en proyectos más grandes, puede ayudar a organizar las solicitudes de incorporación de cambios. Por ejemplo, si hay muchas solicitudes de incorporación de cambios para revisar, se pueden filtrar por hito para que las relaciones públicas de la próxima versión se revisen antes. Y las etiquetas pueden proporcionar más contexto al indicar errores, funciones, mejoras, etc.
Revise su propio código antes de asignar otros revisores. Sí, acabas de escribir este código y aún recuerdas todo. Pero darle una nueva apariencia, especialmente en la GUI del cliente Git, puede ayudarlo a encontrar algunas cosas que puede haber pasado por alto anteriormente.
Aborde y resuelva todos los comentarios antes de la fusión. De esta manera, no dejará que nadie adivine qué se implementó y qué no. Y al abordar no quiero decir que tengas que implementar cada comentario, pero al menos deja una respuesta o reacciona con un emoji para indicar que se manejó o no se manejará. Luego, el revisor puede resolver la conversación. GitHub incluso tiene una configuración que puede habilitar que bloquea la fusión a menos que se hayan resuelto todas las conversaciones.
A continuación, puede encontrar una lista muy pequeña de otras cosas que puede hacer para mejorar la revisión y la calidad del código.
Al escribir código, trate de leerlo. Si tiene que calcular mucho mientras lee, entonces considere refactorizarlo. Computar código sobre la marcha significa mantener muchas cosas en la memoria, y la memoria de trabajo humana es muy limitada. Esto significa que cuantos más cálculos necesite realizar, mayor será la probabilidad de que se pierda algo del panorama general, lo que hará que la revisión del código sea menos efectiva. Como revisor, si me resulta difícil mantener en la memoria todo lo que está sucediendo, pido refactorizar el código. (Algunos ejemplos de código que necesita refactorización son comprobaciones booleanas complejas o cuerpos de métodos largos).
Tenga una lista de verificación de cosas para revisar y probar antes de abrir una solicitud de extracción. Para una aplicación móvil, esto puede incluir configuraciones como retrato y paisaje, niveles de API mínimos y máximos del sistema operativo, sabores y temas. Tengo una publicación sobre mi propia lista de verificación , que es muy antigua pero aún relevante.
GitHub tiene una función llamada borrador de solicitudes de extracción. Si desea una revisión temprana, quizás sobre una característica complicada, pero el código aún no está listo para la revisión, puede marcar la solicitud de incorporación de cambios como borrador. Creo que esta es una solución más ordenada que prefijar PR con títulos como WIP
.
Integre otras herramientas para controlar la calidad del código. Para los desarrolladores de Flutter, estos pueden ser Dart Code Metrics , Spec de Invertase o varias herramientas de Very Good Ventures .
Ahora configuremos un proyecto de muestra que muestre algunos de estos consejo
https://github.com/darjaorlova/codemagic_pr_todo
Codemagic tiene una opción de Editor de flujo de trabajo para Flutter, lo que hace que sea muy fácil configurar una canalización de CI/CD completa para proyectos de Flutter utilizando una GUI bastante intuitiva. Entonces, en aras de la simplicidad, usaremos el Editor de flujo de trabajo de Codemagic aquí.
En Codemagic, haga clic en Duplicar flujo de trabajo .
Haga clic en el nombre del flujo de trabajo y cámbielo a Pull request
.
En el editor de flujo de trabajo de Codemagic, seleccione la casilla de verificación Ejecutar solo pruebas .
Expanda la pestaña Desencadenadores de compilación y marque las siguientes casillas de verificación:
Expanda la pestaña Pruebas y asegúrese de que la opción Detener compilación si fallan las pruebas o el análisis esté marcada.
Marque la casilla de verificación Habilitar analizador Flutter en la pestaña Análisis de código estático .
Marque la casilla de verificación Habilitar prueba Flutter en la pestaña Pruebas unitarias e integración .
Y eso es todo para la configuración del flujo de trabajo. ¡No olvides guardar los cambios!
Comencemos nuestra primera compilación para asegurarnos de que funcione.
Importante: Las reglas de protección de sucursales solo se pueden establecer si tiene una cuenta de GitHub Pro o Teams.
Abra Configuración → Sucursales → Agregar regla .
Marca todas las casillas que necesites. En nuestro caso, son:
Complete el nombre de la sucursal y guarde.
En este punto, nuestra base de código consiste en el ejemplo básico de la aplicación de contador Flutter. Probemos la integración:
test_codemagic_integration
. (Aquí test
hay un prefijo que usaré para las sucursales que están probando algo sin introducir funciones).Este es el PR: https://github.com/darjaorlova/codemagic_pr_todo/pull/1
Y si lo comprobamos en la consola de Codemagic, veremos que la compilación falló:
En los detalles de la compilación, podemos ver que el análisis de Flutter ha encontrado problemas .
En este momento, es posible que tenga un par de preguntas: ¿Cómo sabía Codemagic cuándo ejecutar el flujo de trabajo y cómo informaba a GitHub? La respuesta es a través de webhooks . Se agregó automáticamente un webhook cuando conectamos nuestro proyecto a GitHub y, en los primeros pasos, configuramos los disparadores para ese webhook.
Puede verificarlo en la configuración del proyecto Codemagic:
Y en GitHub: Configuración → Webhooks
Esta es una verificación de estado que establecemos según lo requerido en las reglas de protección de sucursales.
pull_request_template.md
.main
).## Description
1. What type of code this is (e.g., bug fix, new feature)
2. What it does and how it does it
3. Why it is done that way (if required)
4. Nice to have: images/videos (if it involves the UI)
## Checklist
- [ ] Has unit tests
- [ ] Coverage is at least 90%
- [ ] Has documentation
- [ ] Has release milestone
La próxima vez que abramos una solicitud de extracción, ya estará precargada para nosotros:
Y el resultado se ve así: https://github.com/darjaorlova/codemagic_pr_todo/pull/3
Ya hemos terminado con la integración. Espero que hayas aprendido algo nuevo. Estos consejos para hacer que las solicitudes de incorporación de cambios sean más claras y fáciles de revisar ayudarán a simplificar su vida como desarrollador. ¡Feliz solicitud de extracción!
Si desea replicar este proyecto y ver ejemplos, el repositorio de GitHub con el codemagic.yaml
archivo está disponible aquí .
Esta historia se publicó originalmente en https://blog.codemagic.io/10-tips-for-better-pull-requests-and-code-review/
1654828260
コードレビューは、ソフトウェア開発の世界で広く採用されているアプローチです。仲間の開発者にあなたのコードをチェックさせる(そして彼らのコードを順番にチェックさせる)ことは、間違いを排除し、コードベースをクリーンアップし、チーム全体で知識を共有するのに役立ちます。しかし、それでも役立つのですが、コードレビューは依然として非常にストレスがかかり、時間がかかる可能性があります。この記事では、コードレビュープロセスを可能な限りシンプルで苦痛のないものにするためのヒントとアイデアを共有したいと思います。レビューする人とレビューのためにコードを準備する人の両方にとってです。
プルリクエストとコードレビューの必要性については議論が交わされており、一部の開発者はペアプログラミングとmobプログラミングを代替案として提案しています。(ウィキペディア:Mobプログラミングは、チーム全体が同じこと、同時に、同じスペース、同じコンピューターで作業するソフトウェア開発アプローチです)。
この記事の目的はこれらのアプローチを比較することではありませんが、コードレビュープロセスのいくつかの利点を強調したいと思います。
プルリクエスト、レビュー担当者の好み、利用可能な時間などに応じて、コードレビューへのさまざまなアプローチに遭遇しました。
コードレビューは、純粋に人間のコミュニケーションプロセスです。そして、人間(特にあなたが一緒に働く必要がある人間)にアプローチする良い方法は、共感することです。これには明らかなことも含まれます:礼儀正しさ、優しさ、そして敬意。しかし、さらに一歩進んで、コードを調べるためにレビュー担当者を割り当てるときは、他の人の時間とエネルギーを要求していることを覚えておいてください。したがって、プロセスを簡単にするのは良いことであり、そうすることで、おそらくより効率的なコードレビューが得られます。ですから、共感を念頭に置いて、コードレビューアを幸せにするための実用的なヒントについて話し合いましょう。
一般に、これらのヒントはすべて、ほぼすべてのタイプの開発およびバージョン管理システム(VCS)に適用できます。この記事は、理論と実践の2つの部分で構成されます。実際の例として、GitHubをGitクライアントとして使用し、CodemagicをCI/CDツールとして使用してFlutterアプリケーションを作成します。
これで、タスクマネージャーを開き、チケットを選択して、「進行状況」に移動しました。次は何ですか?
作業するブランチを作成します。そして、そのブランチを…何と呼びますか?
ブランチ名は、タスクのコンテキストを与える最初の機会です。レビュー担当者がコードをローカルでチェックアウトすることを決定した場合、レビュー担当者は他の多くのブランチの中からそのブランチを見つける必要があります。
作業の管理方法に応じて、いくつかの優れたアプローチから選択できます。
アプローチ1:
タスクにプレフィックスまたは番号を付けるタスク管理ツール(Jiraなど)を使用している場合は、そのプレフィックスを使用することもできます。特に、チケットを「コードレビュー」に移動するなどのフックを設定する場合に使用できます。 PRが開かれるか、PRがマージされるときに「テストする」。たとえば、タスク名がABC-57: Add list with purchases
、の場合、ブランチの適切な名前は。になりますABC-57_purchases_list
。
しかし、なぜだけではないのABC-57
ですか?
では、これら2つのオプションを比較してみましょう。
ブランチのリストA: | ブランチBのリスト: |
---|---|
ABC-57 | ABC-57_purchases_list |
ABC-64 | ABC-64_fix_cart_npe |
ABC-73 | ABC-73_migrate_2.8 |
これらのリストのうち、必要なブランチをより早く見つけることができるのはどれですか?説明を含む命名規則を使用すると、チケット番号を覚えたり調べたりする必要がないため、自分のブランチを定期的に切り替えるときにも役立ちます。
アプローチ2:
同じprefix
+アプローチを使用しますが、名前の前にチケット番号を付ける代わりに、、、、description
などのプレフィックスを付けます。このアプローチは、使用可能なチケット名がない場合にのみ使用することをお勧めします。次にいくつかの例を示します。featurebug_fixrefactor
feature_purchases_list
またbug_fix_cart_npe
アプローチ3:
一貫性があり、論理構造を持っている限り、他のブランチ命名戦略は問題ありません。ブランチにランダムに名前を付けると、最終的には混乱につながる可能性があります。それらの間を移動するのが難しいだけでなく、維持するのもより困難です。どのブランチが古くなっていますか?それらを削除できますか?マージされていない作業はありますか?それはまだ関連していますか?
使用できるコードレビュー戦略の1つは、コミットごとのレビューですが、コミット履歴によって可能になる場合に限ります。これは、コミットに正しく名前を付ける方法についての非常に優れた詳細な記事です。
要するに、ここにいくつかのガイドラインがあります:
Update headline text color
またはRemove unused imports
。プルリクエストのコミット履歴を読み取るだけで、レビュー担当者は、コードが1行表示される前であっても、レビュー対象についてある程度理解できます。
さらに一歩進めたい場合は、最近、コミットに対する興味深いアプローチに遭遇しました。このアプローチのコンテキストは、単に名前をコミットするだけではありません。あなたはそれについてのすべてをここで読むことができます。
ただし、一般的な考え方は、コーディングを開始する前にコミット名を記述することです。これにより、一度に1つのステップに集中でき、何をする必要があるかがより明確になり、暗黙的にコミットをアトミックにすることができます。これは非常に興味深いテイクであり、自分で試してみる計画だと思います。
最初に覚えておくべきことは、レビュー担当者とは異なり、現在、タスクとソリューションのコンテキストに没頭しているということです。しかし、レビュー担当者がプルリクエストを開くと、レビュー担当者が持っているのはあなたが提供したものだけです。彼らはコードを書かず、問題に取り組むのにX時間も何日も費やしませんでした。したがって、レビューをより有用なものにするために、可能な限り多くのコンテキストを提供することが重要です。
説明
説明の作成について話すとき、JiraチケットをPRにリンクして、それで完了するという意味ではありません。これには、レビュー担当者がJiraに切り替えて説明を読む必要があります。これには、コードレビューに必要な情報よりも多くの情報が含まれている可能性がありますが、ソリューションの実装方法についてはわかりません。
簡単な説明を提供し、次の3つの質問に答えることで、これを回避できます。
この説明のおかげで、レビュー担当者はコードで何を探すべきか、そしてなぜそれがそのように書かれたのかを知ることができます。これにより、より関連性の高いコメントを提供できます。
これを確実にするには、GitHubにプルリクエストテンプレートを追加します。
画像と動画
実際の結果がどのように見えるかを知りながらコードを評価することは、想像するよりもはるかに簡単です。タスクがUIの変更に関連している場合、最終結果の画像(またはフローの場合はビデオ)を追加すると、レビュー担当者がコードを理解するのに非常に役立ちます。または、タスクに複雑なロジックが含まれる場合は、アルゴリズムを説明するシーケンス図を添付できます。
プルリクエストが大きいほど、コードレビューは貧弱になります。コードレビューは精神的に難しいプロセスです。コードを読み、コードが何をしているのかを理解し、それがどのように行われているのかを理解し、潜在的な問題を探す必要があります。覚えておく必要のあるコード行が多いほど、何かを見落とす可能性が高くなります。実際には「黄金」の行数はありませんが、500未満に固執することをお勧めします。それ以上ある場合は、それらを複数のプルリクエストに分割します。
いいえ、ここではアーキテクチャについて話していません。TODO
開発中は、印刷ログの生成、特定の値のハードコーディング、 sの残しなど、あらゆる種類のトリックを使用します。時間を節約し、レビュー担当者が手元の機能に集中できるようにするには、レビュー担当者を割り当てる前に、プルリクエストがクリーンであることを確認する習慣をつけてください。清潔に保つとはどういう意味ですか?
TODO
:をどうするかを決める上級者向けのヒント:これらの問題のほとんどは静的コード分析ツールで解決できますが、処理方法によっては、カスタムルールの実装が必要なものもあります。
機能が完了し、コードが最終的なものであり、後でマージの競合を残すことができるため、これは些細なことのように思えるかもしれません…しかし、それが常に正しいとは限りません。ブランチがベースブランチから大きく分岐している可能性があります。または、一部の変更がコードと衝突して、リファクタリングなしでマージできない場合があります。そして、これには別のレビューが必要になるか、見落とされている可能性のある問題と統合されます。
JSONモデルや単体テストモックなどのコード生成ファイルを確認する必要はありません。したがって、それらをdiffで明示的に表示しても、メリットはなく、単に煩わしいだけです。Gitクライアントが異なれば、そのようなファイルをレビューから除外するアプローチも異なります。たとえば、GitHubでは、パターンをに追加.gitattributes
でき、最終的な差分で折りたたまれます。
静的コード分析を有効にすることをお勧めします。Dartのドキュメントでは、その特典について非常に簡潔に説明しています。
静的分析を使用すると、1行のコードを実行する前に問題を見つけることができます。これは、バグを防ぎ、コードがスタイルガイドラインに準拠していることを確認するために使用される強力なツールです。
新しいDartおよびFlutterプロジェクトでは、デフォルトでlintルールが有効になっています。ここで、既存のプロジェクトでそれらを有効にする方法を読むことができます。ここでダートとここでフラッターのルールをチェックしてください。独自のルールを作成することも、既存のルールを無効にすることもできます。
レビューアを割り当てる前に、コードがlintルールに違反していないことを確認してください。CIを使用してこれを確認できます。lintの警告がなくなるまで、マージの可能性をブロックします。
プロのヒント: DartCodeMetricsなどのツールを使用してさらに先に進むことができます。
単体テストは、テストはもちろんのこと、他の誰かがコードを見る前に、開発の初期段階で問題を特定してバグを見つけるのに役立ちます。さらに、コードが変更された場合でもコードの動作が同じになるようにします。ボーナスとして、それらはまたあなたがよりクリーンでより分離されたコードを書くことを引き起こし、全体的なコード品質に貢献します。
すべてのプルリクエストで単体テストを実行し、何かが失敗した場合にマージをブロックするようにCIを設定できます。また、プルリクエストをレビューの準備ができているとマークする前に、失敗したテストを修正する必要があります。
プロのヒント:Codecovなどのツールを使用してコードカバレッジレポートを有効にします。
マイルストーンやラベルなどのマーカーを使用することは、より便利なヒントであり、プロジェクトのサイズによっては省略できます。ただし、大規模なプロジェクトでは、プルリクエストを整理するのに役立ちます。たとえば、確認するプルリクエストが多数ある場合は、マイルストーンでフィルタリングして、次のリリースのPRをより早く確認できます。また、ラベルは、バグ、機能、拡張機能などを示すことで、より多くのコンテキストを提供できます。
他のレビューアを割り当てる前に、自分のコードをレビューしてください。はい、あなたはこのコードを書いたばかりで、まだすべてを覚えています。ただし、特にGitクライアントのGUIで見た目を新しくすることで、以前は見落としていた可能性のあるものを見つけることができます。
マージする前に、すべてのコメントに対処して解決してください。このようにして、何が実装され、何が実装されなかったかをだれにも推測させないようにします。また、アドレス指定とは、すべてのコメントを実装する必要があるという意味ではありませんが、少なくとも返信を残すか、絵文字に反応して、処理されたか処理されないかを示します。その後、レビュー担当者は会話を解決できます。GitHubには、すべての会話が解決されない限り、マージをブロックすることを有効にできる設定もあります。
以下に、コードレビューと品質を改善するために実行できる他のことの非常に小さなリストを示します。
コードを書くときは、読んでみてください。読みながら多くの計算を行う必要がある場合は、リファクタリングを検討してください。外出先でコードを計算するということは、多くのものをメモリに保持することを意味し、人間の作業メモリは非常に限られています。これは、実行する必要のある計算が多いほど、全体像から何かを見逃す可能性が高くなり、コードレビューの効果が低下することを意味します。レビュー担当者として、進行中のすべてをメモリに保持するのが難しい場合は、コードをリファクタリングするように依頼します。(リファクタリングが必要なコードの例としては、複雑なブールチェックや長いメソッド本体があります。)
プルリクエストを開く前に、確認およびテストするチェックリストを用意してください。モバイルアプリの場合、これには、縦向きと横向き、OSの最小および最大APIレベル、フレーバー、テーマなどの構成を含めることができます。自分のチェックリストについての投稿があります。これは非常に古いものですが、依然として関連性があります。
GitHubには、ドラフトプルリクエストと呼ばれる機能があります。おそらく複雑な機能についての早期レビューが必要であるが、コードがまだレビューの準備ができていない場合は、プルリクエストをドラフトとしてマークできます。これは、PRの前に。などのタイトルを付けるよりも優れたソリューションだと思いますWIP
。
コード品質を制御するための他のツールを統合します。Flutter開発者の場合、これらはDart Code Metrics、 InvertaseのSpec、またはVeryGoodVenturesのさまざまなツールである可能性があります。
次に、これらのヒントのいくつかを紹介するサンプルプロジェクトを設定しましょう。
https://github.com/darjaorlova/codemagic_pr_todo
CodemagicにはFlutter用のワークフローエディターオプションがあり、かなり直感的なGUIを使用してFlutterプロジェクト用の本格的なCI/CDパイプラインを非常に簡単にセットアップできます。したがって、簡単にするために、ここではCodemagicのワークフローエディタを使用します。
Codemagicで、[ワークフローの複製]をクリックします。
ワークフロー名をクリックして、に変更しPull request
ます。
Codemagicのワークフローエディタで、[テストのみを実行]チェックボックスを選択します。
[ビルドトリガー]タブを展開し、次のチェックボックスをオンにします。
[テスト]タブを展開し、[テストまたは分析が失敗した場合はビルドを停止する]チェックボックスがオンになっていることを確認します。
[静的コード分析]タブの[フラッターアナライザーを有効にする]チェックボックスをオンにします。
[統合と単体テスト]タブの[フラッターテストを有効にする]チェックボックスをオンにします。
ワークフローの設定は以上です。変更を保存することを忘れないでください!
最初のビルドを開始して、動作することを確認しましょう。
重要:ブランチ保護ルールは、GitHubProまたはTeamsアカウントをお持ちの場合にのみ設定できます。
[設定] → [ブランチ] → [ルールの追加]を開きます。
必要なすべてのチェックボックスをオンにします。私たちの場合、それらは次のとおりです。
ブランチ名を入力して保存します。
この時点で、コードベースは基本的なFlutterカウンターアプリの例で構成されています。統合をテストしてみましょう:
test_codemagic_integration
。(ここでtest
は、機能を導入せずに何かをテストしているブランチに使用するプレフィックスです。)これはPRです:https ://github.com/darjaorlova/codemagic_pr_todo/pull/1
また、Codemagicコンソールで確認すると、ビルドが失敗したことがわかります。
ビルドの詳細で、Flutteranalyzeが問題を発見したことがわかります。
現時点では、いくつか質問があるかもしれません。Codemagicは、ワークフローを実行するタイミングをどのように認識し、GitHubにどのようにレポートしましたか。答えはwebhook経由です。プロジェクトをGitHubに接続すると、Webhookが自動的に追加され、最初のステップで、そのWebhookのトリガーを設定しました。
Codemagicプロジェクトの設定で確認できます。
そしてGitHubで:設定→ Webhook
これは、ブランチ保護ルールで必要に応じて設定したステータスチェックです。
pull_request_template.md
。main
)。## Description1. What type of code this is (e.g., bug fix, new feature)2. What it does and how it does it3. Why it is done that way (if required)4. Nice to have: images/videos (if it involves the UI) ## Checklist- [ ] Has unit tests- [ ] Coverage is at least 90%- [ ] Has documentation - [ ] Has release milestone
次回プルリクエストを開くと、すでに事前に入力されています。
結果は次のようになります:https ://github.com/darjaorlova/codemagic_pr_todo/pull/3
これで統合は完了です。あなたが何か新しいことを学んだことを願っています。プルリクエストをより明確でレビューしやすくするためのこれらのヒントは、開発者としての生活を簡素化するのに役立ちます。ハッピープルリクエスト!
このプロジェクトを複製して例を確認したい場合は、codemagic.yaml
ファイルを含むGitHubリポジトリをここから入手できます。
このストーリーは、もともとhttps://blog.codemagic.io/10-tips-for-better-pull-requests-and-code-review/で公開されました
1654773374
配列を反復処理する時間計算量はO(n)です。許容できるようですね。コードのパフォーマンスへの影響を無視し、締め切り前にやるべき仕事がたくさんある場合は、より良いソリューションを考えることに力を入れないかもしれません。しかし、このスキルを使用することが自然な応答になると、コードにネストされたforループまたはネストされたフィルターを記述したことに気付かない可能性があります。これにより、時間計算量がO(n²)に増加し、パフォーマンスが低下する可能性が非常に高くなります。後で発行します。
aとaでの検索の時間計算量はO(1)であり、aでの検索の時間計算量はO(n)であるため、の代わりにSet
、などの非順次コレクションタイプで検索してみてください。DictionaryArraySetdictionaryArray
この記事では、とを使用して速度を最適化する方法を紹介しSet
ますdictionary
。
例1:設定
例1には、すべてのメンバーのデータとプラチナメンバーのIDがあります。目標は、からプラチナメンバーの配列を導出することmembers
ですplatinumMemberIDs
。
let members: [Member]
let platinumMemberIDs:[Int]
の時間計算量filter(_:)
はO(n)であり、配列のインスタンスメソッドの時間計算量もcontains(_:)
O(n)です。
members
次のアプローチは、配列を反復処理します。各反復で、現在のメンバーのIDが含まれているplatinumMemberIDs
かどうかを確認するために検索します。platinumMemberIDs
このアプローチの時間計算量はO(n²)です。
var platinumMembers = members.filter {
platinumMemberIDs.contains($0.id)
}
順序は重要でplatinumMemberIDs
はないため、このデータを非シーケンシャルコレクションタイプに保持できSet
ます。aでの検索の時間計算量Set
はO(1)です。全体的な時間計算量はO(n)に減少します。
let platinumMemberIDSet = Set(platinumMemberIDs)var platinumMembers = members.filter {
platinumMemberIDSet.contains($0.id)
}
例2:辞書
例2では、異なるソースからの2つのユーザーグループがあり、それらの共通部分を調べたいと思います。ユーザーの電子メールが他のグループの別のユーザーと同じである限り、それらは同じユーザーであると見なされます。
let starUsers: [User]
let recentUsers: [User]
最も簡単な方法は、ネストされたforループを記述して重複したユーザーを見つけることですが、時間計算量はO(n²)であり、あまり理想的ではありません。
var duplicatedUsers = [User]()for star in starUsers {
for recent in recentUsers {
if star.email == recent.email {
duplicatedUsers.append(star)
}
}
}
recentUsers
時間の複雑さを軽減するために、各ユーザーの電子メールをキーとして、ユーザーオブジェクトを値として、配列から辞書を作成するだけです。
let recentUserMap = Dictionary(uniqueKeysWithValues: recentUsers.map { ($0.email, $0) })
次に、配列を検索しますが、各反復で、最適化後に重複したユーザーを見つけるために配列をstarUsers
反復処理する必要はありません。recentUsers
代わりに、から直接、現在のスターユーザーと同じメールアドレスを持つ最近のユーザーのユーザーオブジェクトにアクセスしますrecentUserMap
。時間計算量はO(n)に減少します。
let duplicatedUsers = starUsers.filter {
guard let _ = recentUserMap[$0.email] else { return false }
return true
}
このストーリーは、もともとhttps://betterprogramming.pub/how-to-boost-your-ios-code-performance-reduce-searching-an-array-55fbdfee2050で公開されました