[JAVA] Comment écrire du bon code

Aperçu

Directives de codage pour un développement continu par l'équipe. Il n'est pas destiné à un langage spécifique, mais est principalement destiné aux langages orientés objet à typage statique. L'exemple de code est décrit dans Swift, sauf indication contraire.

Objectif de la directive

La description de contenus

Le [: star: number] au début du titre est l'importance. Plus la valeur est élevée, plus l'effet sur le système est important, mais plus la valeur est basse, plus l'effet est petit et plus il est facile à réparer.


[: Star: 5] Réduisez la portée des variables

Les valeurs de variable créent de la complexité et conduisent à des malentendus et des bogues, donc moins un programme a de variables, moins il est susceptible de causer des problèmes. En règle générale de programmation, gardez les variables minimales nécessaires et ne les augmentez pas inutilement.

De plus, plus la portée et la durée de vie d'une variable sont grandes, plus l'effet indésirable est important, donc lorsque vous utilisez une variable, essayez de minimiser la portée et la durée de vie. Fondamentalement, ceux ci-dessus ont une portée plus large, alors essayez d'éviter de les utiliser.

  1. Variables globales
  2. Variables d'instance (variables de membre de classe)
  3. Variables locales

Variables globales

Comme mentionné ci-dessus, plus la portée d'une variable est grande, plus le dommage est important, évitez donc d'utiliser la variable globale avec la plus grande portée autant que possible.

Que sont les variables globales

Les variables qui peuvent être lues et écrites de n'importe où sont appelées variables globales.

En plus des variables dites globales telles que le langage C, les variables statiques (variables de classe) telles que Java sont souvent appelées variables globales. De plus, j'élargirai un peu plus le sens ici, et je poursuivrai la discussion sur les singletons et les objets partagés auxquels on peut accéder de n'importe où en tant que variables globales.

De plus, le stockage tel que les bases de données et les fichiers a la même propriété d'être lisible et inscriptible de n'importe où, donc bien qu'il soit légèrement irrégulier, il est traité ici comme une sorte de variable globale.

Pourquoi les variables globales sont mauvaises

Les variables globales sont susceptibles de créer les problèmes suivants en raison de leur capacité à lire et à écrire des valeurs de n'importe où.

De plus, la présence de variables globales peut permettre à des classes et des modules qui ne seraient autrement pas liés les uns aux autres de s'influencer mutuellement par le biais des variables globales (ce que l'on appelle des états étroitement couplés). En conséquence, il est difficile de réutiliser des classes et des modules, il est difficile d'isoler les problèmes et il est difficile d'effectuer des tests unitaires.

Ne devriez-vous pas du tout l'utiliser?

Alors pourquoi ne pas utiliser du tout des variables globales? Ce n'est pas le cas. Comme mentionné ci-dessus, les singletons, les objets partagés et les bases de données sont également définis comme un type de variable globale, mais il est difficile de créer une application sans les utiliser du tout.

Les applications ont souvent des informations contextuelles qui sont généralement nécessaires, telles que les paramètres et les informations de session. Il est plus facile d'implémenter de telles informations en fournissant une méthode accessible de n'importe où, comme une variable globale, plutôt que de transmettre ces informations à un autre objet comme un relais de compartiment en tant qu'argument de fonction.

Cependant, la politique consistant à éviter autant que possible l'utilisation de variables globales reste inchangée. Examinez attentivement la conception globale et assurez-vous que seules les méthodes d'accès globales minimales nécessaires sont disponibles.

Variables globales backend

Dans les applications WEB (PHP, Rails, SpringMVC, ASP, etc.) qui génèrent du HTML côté serveur, les DB et les sessions jouent le rôle de variables globales, donc au sens strict, les variables dites globales (variables globales PHP et Les variables statiques Java, etc.) sont rarement nécessaires. Si vous utilisez des variables dites globales dans votre application WEB, il y a de fortes chances qu'il y ait un problème de conception.

En outre, il semble que les processus à court terme tels que le traitement par lots nécessitent rarement des variables dites globales hors DB.

Variables globales frontales

D'autre part, dans les applications frontales telles que les smartphones, les applications de bureau et les SPA, les variables globales sont souvent préparées et utilisées par elles-mêmes. Dans un tel cas, évitez l'utilisation suivante.

D'un autre côté, les variables globales existent en permanence plutôt que temporairement, sont fournies pour des informations couramment utilisées par diverses fonctionnalités de l'application et devraient être utilisées si l'accès à certaines classes est restreint. Ça va être bien.

Points lors de l'utilisation de variables globales

Lors de la création d'une variable globale ou d'une variable statique, au lieu de contenir un type de données simple tel que Int ou String dans la variable tel quel, faites des données associées un objet et conservez-les sous la forme d'un singleton ou d'un objet partagé. Est bon.

Bad


var userName: String = ""
var loginPassword: String = ""

Good


class AccountInfo {
   static var shared = AccountInfo()

   var userName: String = ""
   var loginPassword: String = ""
}

De plus, si quelqu'un peut ajouter indéfiniment de tels objets partagés, ce sera hors de contrôle, il est donc préférable que certains experts de l'équipe conçoivent les objets partagés.

De plus, même si vous créez une variable globale, vous ne devez pas y faire référence inutilement à partir de différentes classes. Décidez des couches auxquelles vous pouvez accéder aux variables globales et n'y accédez pas à partir d'autres couches.

Objet partagé de singleton

J'ai écrit que les variables globales devraient être sous la forme de singletons ou d'objets partagés, mais en réalité, les objets partagés remplaçables sont meilleurs que les singletons.

Bad


class Sample {
   //Une tonne qui ne peut pas être réaffectée
   static let shared = Sample()
}

Good


class Sample {
   //Objet partagé réaffectable
   static var shared = Sample()
}

Singleton présente les inconvénients suivants car il n'a qu'une seule instance. Ces inconvénients sont en particulier des obstacles à UnitTest et peuvent être problématiques pour la mise en œuvre de l'application.

S'il s'agit d'un objet partagé, il peut éliminer les inconvénients ci-dessus tout en lui donnant une fonction de type singleton. Contrairement aux singletons, les objets partagés ne sont pas mécaniquement garantis d'avoir une instance, mais si vous voulez en avoir une, vous pouvez partager une telle politique de conception au sein de l'équipe de développement.

Article associé [Ne perdez pas la tentation du motif singleton](https://xn--97-273ae6a4irb6e2hsoiozc2g4b8082p.com/%E3%82%A8%E3%83%83%E3%82%BB%E3%82%A4/%E3 % 82% B7% E3% 83% B3% E3% 82% B0% E3% 83% AB% E3% 83% 88% E3% 83% B3% E3% 83% 91% E3% 82% BF% E3% 83 % BC% E3% 83% B3% E3% 81% AE% E8% AA% 98% E6% 83% 91% E3% 81% AB% E8% B2% A0% E3% 81% 91% E3% 81% AA % E3% 81% 84 /)

Considérez les variables globales comme une base de données

--Enregistrer les données associées ensemble en tant qu'objet

En regardant en arrière sur ce qui précède décrit dans les sections précédentes concernant les variables globales utilisant des objets partagés, c'est très similaire au positionnement de DB. Il est facile d'imaginer des variables globales si vous les concevez comme s'il s'agissait d'une base de données ou d'un référentiel prononcé dans le modèle MVC.

Conteneur DI

Sur les plates-formes avec des conteneurs DI (tels que Spring et Angular), il est préférable de gérer les objets partagés avec des conteneurs DI.

Dans un environnement avec un conteneur DI, l'ensemble de l'application a tendance à être conçu pour dépendre du conteneur DI. Que ce soit bon ou mauvais, il est plus facile à comprendre dans un tel environnement si vous laissez la gestion au conteneur DI au lieu de gérer vous-même les objets partagés.

Est-il possible de concevoir sans utiliser du tout de variables globales?

Si vous transmettez toutes les informations nécessaires sous forme d'arguments comme un relais de compartiment, vous pouvez créer une application sans utiliser du tout de variables globales. C'est une forme dans laquelle les informations nécessaires sont transmises de l'extérieur au lieu d'être récupérées par l'utilisateur (ci-après dénommé DI).

Cependant, même sous forme de DI, si vous passez un objet dont l'état peut être changé, cet objet sera changé par plusieurs utilisateurs et traité comme une variable globale, de sorte que les informations passées dans DI ne peuvent pas changer d'état ( Doit être uniquement des objets de valeur non inscriptibles

En passant à fond des objets immuables comme un relais de seau comme celui-ci, il semble que des choses comme les variables globales puissent être complètement éliminées, et à première vue, ce sera un design lâche et beau, mais cette politique En fait, il y a des pièges.

Le problème avec cette politique est que tous les objets médiateurs (passants du relais de compartiment) doivent temporairement disposer des informations nécessaires aux objets à la fin du relais de compartiment. Puisque ce problème nécessite qu'une classe spécifique ait temporairement des informations non pertinentes, on ne peut pas dire qu'elle est faiblement couplée.

Après tout, tout en évitant l'utilisation de variables globales, il n'est pas pratique de ne pas les utiliser du tout, et il peut être préférable d'utiliser des variables globales avec la bonne politique.

Variables d'instance (variables de membre de classe)

Comme pour les variables globales, évitez d'utiliser des variables d'instance autant que possible. Lors de l'ajout d'une nouvelle variable, réfléchissez bien à la question de savoir si elle est absolument nécessaire plutôt que de l'ajouter par hasard. S'il y a deux classes qui implémentent la même fonction, il est important de dire qu'il vaut mieux avoir moins de variables d'instance.

Comment réduire les variables d'instance

J'ai écrit que je devrais éviter autant que possible d'utiliser des variables d'instance, mais quand il s'agit de réduire les variables, les trois suivants sont les bases.

Ne transformez pas les données suffisantes pour l'argument de la fonction en une variable d'instance

Comme c'est souvent le cas avec les débutants, n'utilisez pas seulement des variables d'instance pour réutiliser des données dans plusieurs fonctions. Si les données n'ont pas besoin d'être stockées pendant une longue période, transmettez-les en tant qu'argument de fonction sans utiliser de variable d'instance.

bad


class Foo {
    var user: User?

    func setUser(user: User) {
        self.user = user
        printName()
        printEmail()
    }

    func printName() {
        print(user?.name)
    }

    func printEmail() {
        print(user?.email)
    }
}

good


class Foo {
    func setUser(user: User) {
        printName(user: user)
        printEmail(user: user)
    }

    func printName(user: User) {
        print(user.name)
    }

    func printEmail(user: User) {
        print(user.email)
    }
}

Article associé Utilisation abusive des variables d'instance (que vous pouvez voir autour de vous)

Ne pas contenir la valeur traitée dans la variable d'instance

Cela peut être inévitable pour l'optimisation des performances, mais fondamentalement, il ne contient aucune valeur traitée dans la variable d'instance. Dans l'exemple suivant, ʻitemsA et ʻitemsB sont les valeurs traitées de ʻitems`.

bad


class Foo {
    var items = ["A-1", "A-2", "B-1", "B-2"]
    let itemsA: [String]
    let itemsB: [String]

    init() {
        itemsA = items.filter { $0.hasPrefix("A-") }
        itemsB = items.filter { $0.hasPrefix("B-") }
    }
}

Dans cet exemple, il n'est pas nécessaire de transformer les valeurs de ʻitemsA et ʻitemsB en variables d'instance, faites simplement de ʻitems une variable et de générer ʻitemsA et ʻitemsB` à partir d'une fonction.

good


class Foo {
    var items = ["A-1", "A-2", "B-1", "B-2"]

    func itemsA() -> [String] {
        return items.filter { $0.hasPrefix("A-") }
    }

    func itemsB() -> [String] {
        return items.filter { $0.hasPrefix("B-") }
    }
}

Ne pas contenir d'informations qui peuvent être acquises par programme dans les variables d'instance

Bien que cela souffre un peu de "ne pas contenir la valeur traitée dans la variable d'instance", les informations qui peuvent être jugées à partir d'autres valeurs et les informations qui peuvent être obtenues par une API ou un programme ne sont pas enregistrées dans la variable d'instance, et le programme est exécuté chaque fois que cela est nécessaire. Courez et obtenez.

Utiliser la fermeture pour effacer les variables d'instance

C'est un peu difficile pour les débutants, mais vous pouvez utiliser des fermetures pour réduire les variables d'instance inutiles.

Fondamentalement, les variables d'instance sont utilisées pour conserver les données pendant une longue période, mais les fermetures vous permettent de conserver les données pendant une longue période sans utiliser de variables d'instance. Dans l'exemple suivant, en utilisant le formulaire après, dataType peut être conservé jusqu'à ce que la communication API soit terminée sans utiliser de variables d'instance.

before


class Before {
    var dataType: DataType?

    func fetchData(dataType: DataType) {
        self.dataType = dataType //Enregistrer dans la variable d'instance

        APIConnection(dataType).getData(onComplete: { response in
            self.setResponse(response)
        })
    }

    func setResponse(_ response: Response) {
        print("\(dataType)A été mis en")
    }
}

after


class After {
    func fetchData(dataType: DataType) {
        APIConnection(dataType).getData(onComplete: { response in
            //En utilisant datType dans la fermeture, dataType peut être conservé jusqu'à ce que la communication API soit terminée.
            self.setResponse(response, dataType: dataType)
        })
    }

    func setResponse(_ response: Response, dataType: DataType) {
        print("\(dataType)A été mis en")
    }
}

La programmation fonctionnelle peut être utilisée pour réduire les variables et restreindre la portée des variables. Dans certains langages fonctionnels tels que Haskell, les variables ne peuvent pas être réaffectées en premier lieu, on peut donc dire qu'il n'y a pas de variables dans un sens.

Même si vous utilisez la programmation fonctionnelle, les données seront éventuellement stockées quelque part, mais la portée et la portée de l'influence seront plus petites et vous pourrez écrire du code moins dangereux.

Variables locales

Vous pouvez l'utiliser, mais essayez de minimiser la portée en la définissant uniquement lorsque vous en avez besoin.

Bad


var num = 0
for i in list {
    num = i
    print(num)
}

Good


for i in list {
    let num = i
    print(num)
}

Cependant, dans certains langages (comme le langage C et l'enroulement de var JavaScript), il peut être nécessaire de déclarer des variables au début de la portée.

Les variables explicatives peuvent être utilisées positivement

Pour la lisibilité, il est possible de créer des variables qui ne sont pas nécessairement nécessaires à l'implémentation et d'affecter le résultat de l'expression une seule fois. De telles variables sont appelées «variables explicatives», et contrairement à d'autres variables, elles peuvent être utilisées activement si nécessaire.

Il n'est pas nécessaire de réaffecter la valeur à la variable explicative, et elle est traitée comme une constante, donc l'augmenter ne cause pas beaucoup de mal.

before


let totalPrice = ((orangePrice * orangeQuantity) + (applePrice * appleQuanitity)) *  (1 + taxPercentage / 100)

after


//Les trois suivants sont des variables explicatives
let orangePriceSum = orangePrice * orangeQuantity
let applePriceSum = applePrice * appleQuanitity
let includesTaxRate = 1 + taxPercentage / 100

let totalPrice = (orangePriceSum + applePriceSum) * includesTaxRate

Les variables explicatives augmentent le nombre de lignes de code mais améliorent la lisibilité et ont l'avantage de faciliter le débogage à l'aide de points d'arrêt car vous pouvez voir les résultats au milieu d'une expression.

Raccourcir la durée de vie des variables

Lors du stockage d'un état dans une variable, la durée de vie de la valeur doit être aussi courte que possible. Enregistrez la valeur lorsqu'elle est nécessaire et effacez-la dès que possible lorsqu'elle n'est plus nécessaire. La valeur enregistrée dans la variable est un instantané de ce moment, et il y a un risque qu'elle s'écarte du dernier état au fil du temps, donc la durée de vie de la valeur enregistrée dans la variable doit être raccourcie autant que possible.

[: Star: 5] Principe de source unique

Ne conservez pas les mêmes informations plus d'une fois. Par exemple, dans l'exemple suivant, l'âge est stocké dans deux champs sous des formes différentes et les informations sont dupliquées.

Bad


class Person {
   var age = 17
   var ageText = "17 ans"
}

Dans un tel cas, il est préférable de combiner les informations à conserver comme indiqué ci-dessous et de traiter la valeur d'usage.

Good


class Person {
   var age = 17
   var ageText: String { 
      return "\(age)âge" 
   }
}

Les informations dupliquées ont les effets néfastes suivants sur le système.

――Je ne sais pas laquelle des multiples informations utiliser

Cet exemple est une duplication d'informations simple et facile à comprendre, mais il existe diverses autres formes de duplication d'informations.

Dupliquer par cache

Dans l'exemple suivant, les données de la base de données sont lues et conservées (mises en cache) dans la variable d'instance, mais cela entraîne la duplication des informations contenues dans la variable d'instance et des informations de la base de données.

Bad


class Foo {
   var records: [DBRecord]?
   func readDBRecord(dbTable: DBTable) {
      records = dbTable.selectAllRecords()
   }
}

Il vaut mieux éviter de conserver les données lues à partir du DB dans la variable d'instance comme décrit ci-dessus. Si l'objet «Foo» existe pendant une longue période et que la base de données est mise à jour pendant cette période, il y aura une différence entre les informations de variable d'instance de Foo et les informations de base de données.

Ne codez pas des informations évidentes

Dans l'exemple suivant, 0, 1 et 2 sont utilisés pour la clé Dictionary (Map), mais comme il existe un index s'il est défini sur Array, ces informations sont inutiles si vous souhaitez prendre une commande.

Bad


func getName(index: Int) -> String? {
    let map = [0: "Sato", 1: "Shinagawa", 2: "Suzuki"]
    return map[index]
}

Si vous utilisez Array, vous pouvez accéder par index, vous n'avez donc pas besoin d'informations de 0 à 2.

Good


func getName2(index: Int) -> String? {
    let names = ["Sato", "Shinagawa", "Suzuki"]
    if 0 <= index && index < names.count {
        return names[index]
    }
    return nil
}

Application à autre que la programmation

La politique de ne pas dupliquer les informations est utile non seulement pour la programmation mais aussi pour la gestion de documents.

Bad


Lorsque j'ai copié les spécifications sur la machine locale et que je les ai examinées, les spécifications ont été mises à jour et je codais en fonction des anciennes spécifications.

Il y a des cas où il est nécessaire de copier localement en raison de diverses circonstances, mais dans l'exemple ci-dessus, un problème se produit en raison de la copie et de la duplication des spécifications.

Good


Il n'y a qu'une seule spécification sur le serveur partagé, vous pouvez donc toujours voir les dernières spécifications.

Il n'interdit pas la duplication de code

Le problème dans cette section est de ne pas avoir d'informations en double, ** de ne pas dupliquer du code avec une logique similaire **. Ne vous méprenez pas, car le cas de "copier-coller du code pour créer plusieurs codes similaires" est différent de la duplication ici. Le partage de code avec une logique similaire est une autre histoire, avec une politique de priorité inférieure.

Ne pas avoir plusieurs types d'informations dans un même champ

Inversement, évitez d'avoir plusieurs types d'informations dans un même champ (variables, colonnes DB, interface texte, etc.). Mettre plusieurs types d'informations dans un même champ complique la mise en œuvre et présente le risque de créer divers bogues.

Si vous faites une erreur, ne réutilisez pas une variable à des fins multiples.

[: Star: 5] Donnez un nom approprié

Donnez des noms appropriés aux éléments définis par programme tels que les classes, les propriétés, les fonctions et les constantes. En particulier, le nom de la classe a une grande influence sur la conception et est très important.

Si le nom de la classe, le nom de la propriété et le nom de la fonction peuvent être correctement attribués, il est presque synonyme de conception de classe, de conception de données et d'interface appropriée. Dans un sens, il n'est pas exagéré de dire que la programmation est la tâche de nommer.

Lorsque vous donnez un nom, gardez ce qui suit à l'esprit

――Le but et la signification peuvent être compris à partir du nom même si d'autres le voient

De plus, plus la portée des fonctions et des variables est large, plus les noms doivent être polis.

N'ayez pas plus de rôle que ce qui est écrit dans le nom

Les variables ne font rien de plus que ce qui est écrit dans le nom. La fonction ne fait rien d'autre que ce qui est écrit dans le nom.

Par exemple, dans le cas d'une classe appelée LoginViewController, seul le processus de contrôle de la vue de connexion y est décrit, et d'autres processus tels que l'authentification de connexion ne sont pas décrits en principe. (Cependant, s'il s'agit d'un processus court, il peut être décrit dans le même fichier selon la règle «Garder les éléments liés à proximité»)

C'est juste une question de mettre à jour l'état de quelque chose avec un getter comme getHoge () en Java.

Nommer l'anti-motif

Ajouter des numéros et des identifiants

Bad


var code1 = "a"

func func001() {}

enum VieID {
  case vol_01, vol_02, vol_03
}

Évitez d'utiliser des numéros et des identifiants comme ceux ci-dessus dans les noms de votre programme, car les étrangers ne savent pas ce qu'ils signifient. De plus, si vous utilisez un ID dans votre code, vous devrez modifier le programme si l'ID change.

Omettre le mot

Bad


func chkDispFlg(){}

Comme beaucoup d'entre vous peuvent le comprendre, ce qui précède est une abréviation pour checkDisplayFlag. L'omission de ces mots est une culture traditionnelle des programmeurs, mais dans les temps modernes, il n'y a presque pas de complétion de code par IDE, il n'y a donc pas beaucoup de mérite à l'omettre. N'omettez pas de mots car il sera difficile pour un tiers d'en comprendre le sens.

Dénomination sans signification

Bad


let yen = "Cercle"

Évitez d'utiliser le contenu spécifique de la valeur tel quel car il n'est pas extensible.

Par exemple, si le "yen" ci-dessus est changé en "dollar", le nom de la variable "yen" sera un mensonge. Dans un tel cas, ne le nommez pas par ce qu'est cette constante, mais par quel rôle et quel sens elle a. Dans l'exemple ci-dessus, par exemple, "priceSuffix" peut être considéré à partir du rôle d'un suffixe du montant, ou "currencyUnit" peut être considéré à partir de la signification d'une unité monétaire.

Comment nommer des variables booléennes

Bad


var loadFlag = false

Évitez d'utiliser flag dans le nom de variable de Boolean car le mot flag signifie uniquement qu'il est booléen et ne peut exprimer aucun but ni aucune signification. La dénomination des variables booléennes a le modèle de routine suivant, il est donc bon de suivre cette forme.

--is + adjectifs (isEmpty, etc.) --est + passé participatif (isHidden, etc.) --is + sujet + partie passée (isViewLoaded, etc.) --has + nomenclature (comme hasParent) --can + verbe (canLoad, etc.) --Vibrices (existe, contient, etc.) --should + verb (comme shouldLoad)

Article associé [Comment nommer la méthode et la variable qui renvoient la valeur booléenne](http://kusamakura.hatenablog.com/entry/2016/03/03/boolean_%E5%80%A4%E3%82%92%E8%BF % 94% E5% 8D% B4% E3% 81% 99% E3% 82% 8B% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89% E5% 90% 8D % E3% 80% 81% E5% A4% 89% E6% 95% B0% E5% 90% 8D% E3% 81% AE% E4% BB% 98% E3% 81% 91% E6% 96% B9)

Informations de référence pour l'attribution d'un nom de méthode réussie

Je ne comprends pas l'anglais

Lorsque vous attribuez des noms de variables à des choses dont vous ne connaissez pas le nom anglais, je pense qu'il existe de nombreux cas où vous recherchez d'abord sur le net, mais soyez prudent car les mots ne sont souvent pas traduits correctement dans la traduction automatique telle que la traduction Google. Lorsque vous recherchez de l'anglais, il est préférable de rechercher non seulement la traduction de Google, mais également des exemples de phrases dans les dictionnaires, Wikipedia, etc.

Cependant, si tous les membres de l'équipe ne sont pas bons en anglais et qu'il faut du temps pour chercher l'anglais, c'est une façon d'abandonner l'anglais et d'écrire en japonais romain. C'est un peu maladroit, mais je pense qu'il est important d'avoir la lisibilité et la productivité de tous les membres de l'équipe.

De plus, comme le nom de la fonction du test unitaire est souvent une phrase descriptive, il est bon de l'écrire en japonais si l'environnement peut utiliser le japonais pour le nom de la fonction.

Exemple de nom de fonction de test unitaire Java


public void Un test pour vérifier ce qui se passe quand() {}

Évitez les noms génériques

Évitez autant que possible les noms génériques. Les plus courants sont «en quelque sorte Manager» et «en quelque sorte Controller». Les noms génériques ont tendance à imposer divers processus et la classe a tendance à croître.

Faire un dictionnaire

Lors du développement en équipe, il est recommandé de créer un dictionnaire des termes utilisés dans l'application et de commencer le développement après avoir fait correspondre la reconnaissance des termes avec les membres. La création d'un dictionnaire peut éviter les incohérences dans lesquelles la même chose est définie par des noms différents par les développeurs, et élimine le gaspillage des développeurs individuels qui doivent se soucier de nommer la même chose séparément.

Article associé Faites attention aux parties anglaises lorsque vous nommez des modèles et des méthodes Anti-modèle de dénomination de classe

Focus sur la cohérence du style de code

Les styles de code tels que les conventions de dénomination des classes et des variables doivent être unifiés et cohérents au sein du projet. L'utilisation de noms et de styles exceptionnels pour certaines parties d'un projet peut réduire la lisibilité et entraîner des malentendus et des oublis.

Par exemple, si vous avez déjà les classes View001, View002 et View003, lorsque vous ajoutez la classe View la prochaine fois, il est préférable d'unifier la méthode de dénomination à View004. Cela va à l'encontre de la section "Ne pas utiliser de symboles ou d'identifiants dans les noms" ci-dessus, mais il est plus important d'avoir des noms cohérents dans le projet.

Si vous rencontrez des problèmes avec vos conventions ou styles de dénomination actuels et que vous souhaitez les modifier, il est judicieux d'obtenir le consentement des membres de votre équipe et de les corriger tous en même temps, pas seulement certains.

[: Star: 5] Préférez la forme de object.function () à la forme de fonction (objet)

Je préfère la forme ʻobject.function () qui appelle la fonction de l'objet à la forme function (object) `qui passe l'objet à la fonction.

Bad


if StringUtils.isEmpty(string) {}

Good


if string.isEmpty() {}

Il y a plusieurs raisons à cela et seront expliquées ci-dessous.

Pour la lisibilité

La forme fonction (objet) qui passe un objet à une fonction est difficile à lire car les parenthèses sont imbriquées lorsque plusieurs processus sont répétés, mais la forme qui appelle une fonction objet ʻobject.function () `est reliée par des points et plusieurs Bonne lisibilité car il peut être traité.

Bad


StringUtils.convertC(StringUtils.convertB(StringUtils.convertA(string)))

Good


string.convertA().convertB().convertC()

object.function () est plus réutilisable

Généralement, dans le cas de «fonction (objet)», la fonction est définie dans une classe ou un module, et pour effectuer le traitement, deux classes ou modules dans lesquels la fonction est définie et l'objet comme argument sont nécessaires. D'autre part, dans le cas de ʻobject.function () `, il est hautement réutilisable car il peut être traité par l'objet seul.

Acquérir l'orientation de l'objet

** La forme de object.function () est l'essence même de l'orientation objet **.

Lorsque nous parlons d'orientation objet, les classes, l'héritage, l'encapsulation, etc. sont souvent expliqués en premier, mais en réalité ils ne sont pas essentiels pour l'orientation objet, et la seule chose qui est nécessaire pour l'orientation objet est d'appeler une méthode sur un objet ʻobjet. Je pense que c'est juste la forme de function () `.

Enseigner l'orientation objet aux débutants est difficile. Il semble y avoir beaucoup à apprendre sur l'orientation des objets. Cependant, oublier l'héritage, l'encapsulation, le polymorphisme, etc., et laisser la forme de ʻobject.function () `pénétrer dans le corps est le chemin le plus court pour acquérir l'orientation de l'objet.

Article associé [Orienté objet est l'une des méthodes d'envoi de méthodes](https://qiita.com/shibukawa/items/2698b980933367ad93b4#%E3%82%AA%E3%83%96%E3%82%B8%E3%82 % A7% E3% 82% AF% E3% 83% 88% E6% 8C% 87% E5% 90% 91% E3% 81% AF% E3% 83% A1% E3% 82% BD% E3% 83% 83 % E3% 83% 89% E3% 83% 87% E3% 82% A3% E3% 82% B9% E3% 83% 91% E3% 83% 83% E3% 83% 81% E3% 81% AE% E6 % 89% 8B% E6% B3% 95% E3% 81% AE1% E3% 81% A4)

Utiliser activement la propriété calculée

Selon la langue, la fonction de propriété calculée permet à la fonction d'être traitée comme une propriété. Transformons positivement les fonctions suivantes en propriétés calculées.

Cas qui ne s'appliquent pas

Ensuite, quand il s'agit de la forme de ʻobject.function () plutôt que de fonction (objet) `à tout moment, cela peut ne pas être le cas.

Vous ne devriez pas prendre la forme de ʻobject.function () si cela donne à la classe de ʻobject des dépendances ou des rôles inutiles que vous ne devriez pas avoir.

L'exemple suivant prend la forme de ʻobject.function () , créant une dépendance inutile sur la classe View pour ʻenum APIResult.

Bad


class LoginView: MyView {
    //Recevez le résultat de la connexion et passez à l'écran suivant
    func onReceivedLoginResult(result: APIResult) {
        let nextView = result.nextView() // object.function()Forme de
        showNextView(nextView)
    }
}

enum APIResult {
    case success
    case warning
    case error

    func nextView() -> UIView {
        switch self {
        case .success: return HomeView()
        case .warning: return WarningView()
        case .error: return ErrorView()
        }
        //Cela dépend des classes HomeView, WarningView et ErrorView.
    }
}

Dans un tel exemple, il serait préférable de prendre la forme «fonction (objet)» comme suit.

Good


class LoginView: MyView {
    //Recevez le résultat de la connexion et passez à l'écran suivant
    func onReceivedLoginResult(result: APIResult) {
        let nextView = nextView(result: result) // function(object)Forme de
        showNextView(nextView)
    }

    func nextView(result: APIResult) -> UIView {
        switch result {
        case .success: return HomeView()
        case .warning: return WarningView()
        case .error: return ErrorView()
        }
    }
}

enum APIResult {
    case success
    case warning
    case error
}

La raison pour laquelle vous ne devez pas créer une dépendance comme la première est expliquée en détail dans la section "Conscient du sens de la dépendance" ci-dessous.

[: Star: 5] Préférez l'inclusion et l'interface à l'héritage

Il y a un problème avec l'héritage de classe. L'héritage est une fonctionnalité puissante, mais au prix de nombreux risques. L'héritage de classe est inflexible et vulnérable au changement.

Les modifications affectent les fonctionnalités indépendantes

Par exemple, s'il y a des classes A et B qui héritent de la classe Base, et si Base est modifiée pour corriger la fonction A, la fonction B non liée sera boguée. Cela peut être considéré comme un problème avec la conception de classe, mais avec l'héritage, de tels problèmes peuvent survenir.

Impossible d'hériter de plusieurs parents

À l'exception de certains langages tels que C ++, plusieurs classes ne peuvent pas être héritées. Cependant, ce qui existe réellement a souvent plusieurs concepts superordonnés (catégories parentes).

Par exemple, tout à coup, si vous pensez au "curry indien", ses parents peuvent être "curry" ou "nourriture indienne".

curry indien de classe étend le curry
curry indien de classe étend la cuisine indienne

Pour donner un exemple qui suit un peu plus le flux de mise en œuvre, disons qu'il existe un système qui vous permet de conclure un contrat smartphone sur le WEB. L'écran de contrat est divisé en deux, "nouveau contrat" et "contrat de transfert (MNP)", et il existe des classes correspondant à chacune. Puisqu'il existe de nombreuses parties communes entre les nouveaux contrats et les contrats de transfert, créez une classe «contrat de base» en tant que classe mère commune.

スクリーンショット 2019-01-25 21.50.45.png

Ensuite, envisagez de mettre en œuvre un écran de changement de contrat. Si vous créez une «nouvelle modification de contrat» qui hérite de la nouvelle classe de contrat et une classe de «changement de contrat de transfert» qui hérite de la classe de contrat de transfert pour la modification de contrat, il ne sera pas possible de standardiser le traitement par héritage du point de vue de la modification de contrat.

スクリーンショット 2019-01-25 21.44.57.png

La classe parent commune a tendance à être gonflée

Par exemple, si vous créez une classe appelée BaseController qui est la classe parente de toutes les classes Controller, la classe BaseController a tendance à être gonflée car elle incorpore les fonctions utilisées par divers Controller. La fonctionnalité minimale requise est suffisante, mais fondamentalement, une classe parente commune doit être préparée pour être traitée comme une interface commune plutôt que comme une fonctionnalité.

Article associé Inconvénients de l'héritage Je ne veux pas créer quelque chose comme BaseViewController dans la conception d'applications iOS Pourquoi la "synthèse" est-elle meilleure que "l'héritage" des classes? Amélioration de la flexibilité et de la lisibilité du code dans le développement de jeux

[: Star: 4] Simplifier la création de branches

La logique de branchement par instruction if ou switch a tendance à altérer la lisibilité du programme et à provoquer des bogues, essayez donc de la garder aussi simple que possible.

N'approfondissez pas le nid

Si l'imbrication (imbrication) de l'instruction if et de l'instruction for devient profonde, le code devient difficile à lire, alors ne rendez pas l'imbrication aussi profonde que possible. Pour éviter une imbrication profonde, il est bon d'effectuer un «retour anticipé» et une «logique de coupure».

J'expliquerai ce qui se passe lorsque «retour anticipé» et «coupure logique» sont appliqués au code suivant à l'aide d'un exemple.

Before


if text != nil {
    if text == "A" {
        //Processus 1
    } else {
        //Processus 2
    }
}

Retour anticipé

En retournant d'abord le cas exceptionnel, l'imbrication de la logique principale est rendue superficielle. Dans l'exemple de code ci-dessous, le cas où le texte est nul est renvoyé en premier en tant que modèle d'exception.

After(Retour anticipé)


if text == nil {
    return
}
if text == "A" {
    //Processus 1
} else {
    //Processus 2
}

Cependant, comme son nom l'indique, le retour anticipé doit être effectué tôt. Fondamentalement, la fonction est censée être traitée jusqu'à la dernière ligne, donc s'il y a un retour au milieu d'une fonction longue, il y a un risque qu'elle soit négligée et provoque un bogue.

Couper la logique

Découpez le traitement, y compris l'imbrication, comme l'instruction if et for, dans des méthodes et des propriétés pour rendre l'imbrication de la logique principale superficielle. Dans l'exemple de code ci-dessous, la partie qui détermine si le texte est "A" et effectue certains traitements est découpée en tant que méthode de classe de la classe Text.

After(Couper la logique)


if text != nil {
   doSomething(text)
}

func doSomething(_ text: String?) {
    if text == "A" {
        //Processus 1
    } else {
        //Processus 2
    }
}

Informations connexes [Réduisez le nombre de nids de blocs](https://qiita.com/hirokidaichi/items/c9a76191216f3cc6c4b2#%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3 % 83% 8D% E3% 82% B9% E3% 83% 88% E3% 81% AE% E6% 95% B0% E3% 82% 92% E6% B8% 9B% E3% 82% 89% E3% 81 % 9D% E3% 81% 86)

Ne pas classer par appelant

Les fonctions appelées à partir de divers endroits ne doivent pas être classées par l'appelant. Par exemple, dans les cas suivants, les cas de traitement sont divisés en fonction de l'écran, mais dans cette méthode d'écriture, la fonction devient infiniment grande à mesure que le nombre d'écrans augmente.

Bad


class BaseViewController: UIViewController {
    func doSomething() {
        if viewId == "home" {
            //Traitement de l'écran d'accueil
        } else if viewId == "login" {
            //Traitement de l'écran de connexion
        } else if viewId == "setting" {
            //Définition du traitement d'écran
        }
    }
}

Si vous écrivez de cette façon, divers processus sont regroupés dans une seule fonction, et il y a de fortes chances que cela devienne une fonction énorme difficile à lire, facile à bogue et difficile à corriger.

Polymorphisme

Les cas dans des fonctions comme celle ci-dessus peuvent être résolus en utilisant le polymorphisme. Une explication détaillée du polymorphisme sera omise car elle sera longue, mais en remplaçant les méthodes par interface (protocole) ou par héritage, chaque processus spécifique au cas peut être décrit dans chaque classe enfant.

class BaseViewController {
    func doSomething() {}
}
class HomeViewController: BaseViewController {
    override func doSomething() {
        //Traitement de l'écran d'accueil
    }
}
class LoginViewController: BaseViewController {
    override func doSomething() {
        //Traitement de l'écran de connexion
    }
}
class SettingViewController: BaseViewController {
    override func doSomething() {
        //Définition du traitement d'écran
    }
}

Passer la fonction

Il est également possible d'éliminer les branches telles que les instructions if en passant une fonction comme argument de la fonction. Si la fonction est reçue comme argument comme indiqué ci-dessous, le traitement de quelque chose peut être librement défini par l'appelant, ainsi le branchement peut être éliminé. Dans cet exemple, il s'agit d'une fonction sans signification qui exécute simplement la fonction reçue, mais dans de nombreux cas, le traitement d'achèvement et le traitement d'erreur qui diffèrent en fonction de l'appelant sont passés en arguments.

class BaseViewController: UIViewController {
    func doSomething(something: () -> Void) {
        something()
    }
}

La même chose peut être obtenue en utilisant des interfaces dans des langages tels que Java qui ne peuvent pas gérer les fonctions comme des objets.

Réduisez le nombre de lignes dans le bloc de branche

Pour rendre la branche plus facile à voir, essayez de réduire autant que possible le nombre de lignes dans le bloc de branche tel que l'instruction if.

if conditionA {
    //N'écrivez pas de longs traitements ici
} else if conditionB {
    //N'écrivez pas de longs traitements ici
}

Commandes et requêtes séparées

Toutes les méthodes doivent être soit des commandes qui exécutent des actions, soit des requêtes qui renvoient des données, pas les deux.

C'est l'idée appelée CQS (command query Separation).

Tout d'abord, n'effectuez aucune action dans une méthode (requête) qui renvoie des données. Il peut être plus facile de comprendre en d'autres termes que le processus de mise à jour n'est pas effectué dans le getter. Je pense que beaucoup de gens gardent cela à l'esprit comme une évidence.

Ensuite, ne décrivez pas la requête dans la méthode (commande) qui exécute l'action autant que possible. C'est plus difficile à comprendre que le premier, et cela peut être différent de ce que dit le chef de famille du CQS, mais je vais l'expliquer avec un exemple.

Par exemple, considérez une fonction qui considère que vous êtes connecté et passe à la page suivante si vous avez à la fois un nom d'utilisateur et un ID de session.

Bad


func nextAction() {
    if userName.isNotEmpty && sessionId.isNotEmpty {
        showNextPage()
    }
}

Considérant que le but principal de cette fonction est de "passer à la page suivante", ce sera une "commande" au lieu d'une "requête", mais la partie qui détermine si vous êtes connecté sera une requête qui obtient la valeur de Bool. , "Commande" et "requête" sont mélangés.

Si la partie de jugement de connexion est coupée de cette fonction comme une requête à une autre fonction, ce sera comme suit.

Good


//commander
func nextAction() {
    if isLoggedIn() {
        showNextPage()
    }
}

//Requete
func isLoggedIn() {
   return userName.isNotEmpty && sessionId.isNotEmpty
}

Si l'expression conditionnelle de l'instruction if est très courte, il est difficile de la couper en une autre fonction, mais s'il s'agit d'une requête d'une certaine longueur, il est préférable de la couper de l'action comme une autre fonction ou propriété.

[: Star: 4] Ne pas agrandir la classe et la fonction

À titre indicatif, la classe doit comprendre environ 50 à 350 lignes et la fonction doit être d'environ 5 à 25. Si elle dépasse cette valeur, envisagez de diviser la classe ou la fonction.

Le nombre de lignes n'est qu'un guide, et il peut être préférable de mettre plus de lignes dans la même classe ou fonction, mais pensons qu'une classe avec plus de 1000 lignes est dangereuse comme bonne limite supérieure.

Cette taille ne peut pas être considérée comme la bonne réponse car elle dépend de la langue, du style d'écriture et de la fonction, mais une approximation de la taille du nombre de lignes dans la classe est décrite.

Nombre de lignes Une sensation de taille
Moins de 50 lignes petit
Lignes 50-350 Approprié
350-700 lignes grand
700-1000 lignes Très grand
Plus de 1000 lignes Dangereux

Cependant, les fonctions sous-jacentes et les classes d'interface utilisateur avec de nombreux éléments peuvent différer du sens de taille mentionné ci-dessus. Par exemple, la classe Android View a 27 000 lignes, et un exemple extrême, un écran avec 1 000 boutons qui font des choses différentes, dépasserait 1 000 lignes, même s'il était écrit brièvement.

Article associé [20 articles pour écrire du bon code que la programmation intermédiaire devrait lire](https://anopara.net/2014/04/11/%E3%83%97%E3%83%AD%E3%82%B0 % E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0% E4% B8% AD% E7% B4% 9A% E8% 80% 85% E3% 81% AB% E8 % AA% AD% E3% 82% 93% E3% 81% A7% E3% 81% BB% E3% 81% 97% E3% 81% 84% E8% 89% AF% E3% 81% 84% E3% 82 % B3% E3% 83% BC% E3% 83% 89 /)

[: Star: 4] Ne pas créer d'indicateurs (propriétés booléennes)

Il est proche de la politique de ne pas avoir de variables d'instance comme décrit dans la section "Réduire la portée des variables", mais essayez de ne pas garder l'indicateur de valeur Bool plus strictement dans les variables d'instance. Les informations détenues par l'indicateur (variable booléenne) sont le résultat du jugement à un certain moment et sont essentiellement les informations passées. Par conséquent, il y a un risque qu'il s'écarte de l'état réel avec le temps.

Bad


class Foo {
    var hasReceived: Bool = false //Drapeaux inutiles
    var data: Data?

    func setData(data: Data) {
        self.data = data
        hasReceived = true
    }
}

Dans l'exemple ci-dessus, si des données ont été reçues ou non est détenu par un indicateur, mais l'indicateur n'est pas nécessaire car vous pouvez voir que les données ont été reçues en regardant la variable data. Évitez de créer de tels indicateurs car cela entraînera une gestion multiple des données.

Afin d'éliminer le drapeau, examinez d'abord s'il est possible de juger dans un autre état au lieu du drapeau. Les drapeaux déterminent un état, mais il est souvent possible de déterminer l'état en regardant autre chose sans le drapeau.

Combinez plusieurs indicateurs en une variable d'état

Lorsqu'il y a plusieurs indicateurs, plusieurs indicateurs peuvent être combinés en une variable Enum en créant un Enum qui représente l'état.

Bad


class User {
    var isAdmin = false
    var isSuperUser = false
    var isGeneralUser = false
}

Good


enum UserType {
    case admin       //Administrateur
    case superUser   //Super utilisateur
    case generalUser //Utilisateur général
}

class User {
    var userType: UserType = .admin
}

Cependant, assurez-vous de ne combiner qu'un seul type d'informations dans Enum, et ** Ne combinez pas plusieurs types d'informations en une seule Enum. ** ** Dans l'exemple ci-dessus, évitez d'ajouter «connecté» à «type d'utilisateur» pour créer l'énumération suivante.

Bad


enum UserLoginType {
    case adminLoggedIn          //Administrateur (connecté)
    case adminNotLoggedIn       //Administrateur (non connecté)
    case superUserLoggedIn      //Super utilisateur (connecté)
    case superUserNotLoggedIn   //Super utilisateur (non connecté)
    case generalUserLoggedIn    //Utilisateur général (connecté)
    case generalUserNotLoggedIn //Utilisateur général (non connecté)
}

[: Star: 4] N'utilisez pas de nombres ou de chaînes de caractères comme clés de données

N'utilisez pas de nombres pour déterminer le branchement conditionnel

Évitez d'utiliser des nombres tels que 0, 1, 2 pour déterminer le branchement conditionnel. Les alternatives varient d'un cas à l'autre, mais l'utilisation de tableaux et de listes évite souvent l'utilisation de nombres.

Bad


func title(index: Int) -> String {
   switch index {
      case 0:
         return "A"
      case 1:
         return "B"
      case 2:
         return "C"
      default:
         return ""
   }
}

Good


let titles = ["A", "B", "C"]

func title(index: Int) -> String {
	return titles.indices.contains(index) ? titles[index] : ""
}

Cependant, 0, 1 et -1 ont souvent des rôles spéciaux tels que l'obtention du premier élément ou l'indication d'une erreur ou d'un succès d'API, ils peuvent donc devoir être utilisés en fonction de l'environnement.

Ne transmettez pas plusieurs types de données dans un tableau

Les langages à typage statique n'utilisent pas de tableaux (Array, List) pour transmettre plusieurs types de données.

La fonction suivante utilise des nombres pour stocker le prénom, le deuxième code postal et la troisième adresse dans la liste, mais il est possible de déterminer quelles informations se trouvent dans quel numéro du type de liste. Parce qu'il ne peut pas être lu, il devient difficile à lire et des erreurs sont susceptibles de se produire.

Bad


func getUserData(): [String: String] {
   var userData: [String] = []
   userData[0] = "Masao Yamada"
   userData[1] = "171-0001"
   userData[2] = "Toyoshima-ku, Tokyo"
   return userData
}

Lorsque vous passez une structure de données fixe, créez une structure ou une classe comme indiqué ci-dessous et transmettez les données.

Good


struct UserData {
   let name: String
   let postalCode: String
   let address: String
}

func getUserData(): UserData {
   return UserData(name: "Masao Yamada", 
                   postalCode: "171-0001", 
                   address: "Toyoshima-ku, Tokyo")
}

Cependant, si vous souhaitez transmettre une liste de noms, une liste de fichiers ou une liste du même type de données, vous pouvez utiliser un tableau ou une liste.

Ne transmettez pas plusieurs types de données dans Map

Pour exactement la même raison, évitez de transmettre plusieurs types de données dans Map (Dictionary). S'il s'agit d'un langage à typage statique, créez une structure ou une classe pour le transfert de données comme dans l'exemple d'un tableau.

Bad


func getUserData(): [String: String] {
   var userData: [String: String] = [:]
   userData["name"] = "Masao Yamada"
   userData["postalCode"] = "171-0001"
   userData["address"] = "Toyoshima-ku, Tokyo"
   return userData
}

[: Star: 4] Ne pas commenter

Supprimez le code dont vous n'avez plus besoin sans le commenter. Il est normal de commenter temporairement pendant un correctif local, mais en gros, ne validez pas ce que vous commentez.

Si le nombre de lignes commentées augmente, le code devient difficile à lire et les parties inutilisées sont capturées lors de la recherche, ce qui est assez dangereux. Vous pouvez voir l'historique des suppressions et des modifications avec des outils de gestion tels que Git, essayez donc de supprimer le code inutile.

[: Star: 3] Soyez conscient du sens de la dépendance

Définissez des règles pour les dépendances entre les programmes afin d'éviter les dépendances non planifiées. C'est une explication abstraite, mais cela dépend des directions suivantes et évite cette direction opposée.

Qu'est-ce que la dépendance?

Cela dépend d'un programme utilisant une autre classe, module, bibliothèque, etc. qu'il ne peut pas compiler ou fonctionner sans lui. En plus de la dépendance au niveau de compilation, on peut dire que cela dépend de la spécification même si elle est créée sur la prémisse d'une spécification spécifique et ne fonctionne pas sans cette spécification.

Dépend des fonctions générales des fonctions dédiées

Les classes à usage général ne doivent pas dépendre de classes dédiées.

Par exemple, dans un cas extrême, si la classe String (fonction générale) qui représente une chaîne de caractères dépend de la classe d'écran de connexion LoginView (fonction dédiée), tous les systèmes qui utilisent String sont LoginView. Il devient nécessaire de gaspiller et il devient difficile de réutiliser la classe String.

Pour donner un autre exemple plus courant, s'il existe une classe HTTPConnection (fonction générale) pour le traitement de la communication, la classe écran (fonction dédiée) unique à l'application est traitée dans cette classe. Si vous l'utilisez pour le jugement, vous ne pourrez pas porter la classe HTTPConnection vers une autre application.

Évitez d'utiliser la propre classe d'écran ou l'ID dédié de l'application pour le jugement dans la classe de fonctions à usage général, car cela peut entraîner un branchement excessif dans la classe à usage général et la compliquer.

Mettez toutes les pièces qui dépendent de la bibliothèque en un seul endroit

Dans les temps modernes, il est naturel d'utiliser une sorte de bibliothèque open source pour implémenter une application, mais le traitement utilisant une bibliothèque spécifique doit être combiné en une classe ou un fichier, et diverses classes ne doivent pas dépendre de la bibliothèque. C'est une image qui ne dépend pas de la surface mais du point.

En consolidant le code qui utilise la bibliothèque en un seul endroit, il est possible de minimiser l'impact et la correction des modifications suivantes.

--Je veux remplacer la bibliothèque que j'utilise par une autre bibliothèque

La classe de données ne dépend pas de fonctions ou de spécifications spécifiques

Bien que cela souffre de ce que j'ai écrit dans la section "Dépendant des fonctions générales des fonctions dédiées", les classes qui visent à conserver des données telles que DTO doivent être aussi simples que possible et dépendre d'autres fonctions. , Il vaut mieux ne pas dépendre d'une spécification spécifique.

//Exemple de classe de données simple
struct UserInfo {
    let id: Int
    let name: String
}

Une classe de données aussi simple peut être utilisée sur de nombreuses couches et parfois portée vers une autre application, mais des dépendances supplémentaires peuvent être préjudiciables.

Utilisez l'interface pour briser la dépendance au concret

De nombreux langages à typage statique peuvent utiliser des interfaces (protocoles) pour éliminer leur dépendance à des classes d'implémentation spécifiques. (Cette section ne s'applique souvent pas aux langages typés dynamiquement car ils n'ont souvent pas d'interface.)

Par exemple, dans l'exemple ci-dessous, la classe «Exemple» dépend de la classe «HTTPConnector» avec laquelle elle communique.

Before


class Example {
    let httpConnector: HTTPConnector

    init(httpConnector: HTTPConnector) {
        self.httpConnector = httpConnector
    }

    func httpPost(url: String, body: Data) {
        httpConnector.post(url: url, body: body)
    }
}

class HTTPConnector {
    func post(url: String, body: Data) {
        //Implémentation de la communication HTTP
    }
}

Si la classe HTTPConnector utilise une sorte de bibliothèque de communication, la classe ʻExampledépendra indirectement de cette bibliothèque de communication. Cependant, si vous changezHTTPConnector en une interface (protocole) comme indiqué ci-dessous, vous pouvez rendre la classe ʻExample indépendante de la bibliothèque de communication.

After


class Example {
    let httpConnector: HTTPConnector

    init(httpConnector: HTTPConnector) {
        self.httpConnector = httpConnector
    }

    func httpPost(url: String, body: Data) {
        httpConnector.post(url: url, body: body)
    }
}

protocol HTTPConnector {
    func post(url: String, body: Data)
}

Consolider la coopération avec les systèmes externes en un seul endroit

Le code lié à la coopération et à l'interface avec des systèmes extérieurs à l'application doit être rassemblé autant que possible en un seul endroit. Divers systèmes externes peuvent être envisagés, mais les plus courants sont les API HTTP et les bases de données.

C'est la même chose que j'ai écrit sur la dépendance aux bibliothèques open source, mais en consolidant le lien avec le système externe en un seul endroit, l'influence et la modification du changement du système externe peuvent être minimisées. Encore une fois, il serait bien d'utiliser une interface (protocole) pour éliminer la dépendance à une classe d'implémentation spécifique.

[: Star: 3] La valeur du code doit être enum

L'énumération est définie pour les constantes numériques, les constantes de chaîne de caractères, les valeurs de code, etc. qui ont plusieurs valeurs.

Bad


func setStatus(status: String) {
    if status == "0" {
        //Traitement en cas de succès
    }
}

Good


enum Status: String {
    case success = "0"
    case error = "1"
}

func setStatus(status: Status) {
    if status == .success {
        //Traitement en cas de succès
    }
}

Lorsque vous devez considérer des valeurs inattendues

Enum ne peut gérer que des valeurs prédéterminées, mais un traitement peut être nécessaire pour des valeurs inattendues telles que les codes d'état d'API. Dans un tel cas, il est préférable de conserver la valeur du code brut comme suit afin qu'Enum puisse être obtenu par getter ou une propriété calculée.

struct Response {
    var statusCode: String
    var status: Status? {
        return Status(rawValue: statusCode)
    }
}

func setResponse(response: Response) {
   if response.status == .success {
      //Traitement en cas de succès
   } else if response.status == .error {
      //Traitement au moment de l'erreur
   } else {
      //Sortie journal de la valeur de code lorsque la valeur de code inattendue arrive
      print("Code d'état: \(response.statusCode)")
   }
}

[: Star: 3] Rédigez un commentaire

Expliquer le code déroutant

Rédigez des commentaires sur un code difficile à voir pour un tiers. Pour le code qui n'a pas immédiatement de sens lorsqu'il est lu par d'autres, comme du code qui répond à des spécifications spéciales ou du code délicat qui corrige des bogues, fournissez une description du code dans les commentaires.

Code spécial pour les corrections de bogues

Il y a des moments où vous ne pouvez pas corriger un bogue de manière simple et vous n'avez pas d'autre choix que de mettre du code sale. Si tel est le cas, laissez un commentaire expliquant pourquoi vous l'avez fait pour les spectateurs ultérieurs.

Par exemple, dans les cas suivants, il est préférable d'écrire une explication dans les commentaires.

nombre magique

Le code ci-dessous nécessite des commentaires car aucun tiers ne sait ce que signifie «3».

Bad


if status == 3 {
   showErrorMessage()
}

Good


//L'état 3 est une erreur de communication
if status == 3 {
   showErrorMessage()
}

Comment écrire des phrases adaptées aux commentaires

Supprimer des mots sans information ou dupliquer des mots

Le japonais est plutôt détourné et écrire des phrases sans réfléchir contient généralement des mots qui ne sont pas informatifs. Après avoir écrit une phrase, relisez-la une fois, vérifiez les mots en double ou les mots inutiles, et s'il y en a, supprimez-les et raccourcissez-les.

Par exemple, les mots suivants peuvent être simplifiés et raccourcis sans changer leur signification.

―― "Peut être raccourci" → "Peut être raccourci"

N'utilisez pas de mots honorables ou de mots polis

En japonais, plus le libellé est poli, plus il y a de caractères. «J'ai fait» a 2 caractères, tandis que «j'ai fait» a 4 caractères. Il y a une question de goût, mais si vous n'en avez pas besoin, vous pouvez raccourcir le commentaire en n'utilisant pas de mots polis ou honorables.

Pour dire aux élèves du primaire et aux étrangers

Essayez de faire des phrases faciles et atteignant plus de gens sans utiliser de mots, de phrases, de kanji et de termes techniques difficiles. Au contraire, il est souvent difficile pour les mots étrangers en katakana d'être transmis aux anglophones, alors soyez prudent.

De plus, il est préférable de ne pas utiliser autant que possible les termes techniques, mais il est normal d'utiliser ce qui est nécessaire pour expliquer le programme. Par exemple, le mot «communication socket» est un terme technique, mais il peut être nécessaire de l'utiliser dans les commentaires des programmes liés à la «communication socket».

Si le projet implique des personnes de différentes nationalités, il est bon d'unifier les commentaires en anglais.

Vous n'êtes pas obligé de mettre des parenthèses

Le plus important est qu'il soit facile à comprendre pour le lecteur. Histoire extrême, si le lecteur peut comprendre ** Peu importe si la grammaire est fausse ou si l'anglais est faux. ** ** Vous n'avez plus besoin de passer du temps à écrire de belles phrases, et Frank va bien, il est donc important d'écrire des commentaires utiles au lecteur.

Mieux que pas de commentaires irresponsables?

Les commentaires suspects suivants peuvent être mieux que de simples informations.

//Il y a une fuite de mémoire ici.

//La branche ici n'a peut-être pas beaucoup de sens. ..

//Je ne sais pas pourquoi cela fonctionne, mais pour le moment, ce code a résolu le problème ici

[: Star: 3] Soyez conscient de la disponibilité du service

Ne plantez pas l'application

En règle générale, ** l'application ne doit pas tomber **.

Cela peut sembler évident, mais les langages de programmation modernes peuvent facilement planter. Par exemple, de nombreuses langues planteront si vous accédez au 4ème dans un tableau avec 3 éléments. Ce genre de bogue est facile à se produire, et il n'est pas rare que certains soient publiés inaperçus.

Il y a une bonne idée que vous devriez lever une exception si elle est dans un mauvais état, mais il n'est pas bon que l'application entière plante.

Dans les programmes d'arrière-plan tels que les API et les lots, les exceptions arrêtent rarement l'ensemble du système, il est donc bon d'utiliser activement les exceptions, mais dans les applications frontales, une exception arrête complètement l'application. Comme cela arrive souvent, il vaut mieux ne pas lancer d'exception autant que possible.

Afin d'éviter autant que possible un tel crash, les mesures suivantes sont nécessaires.

Mettre en place la protection appropriée

Ajoutez une instruction if au code qui peut se bloquer, et si elle se bloque, arrêtez le traitement. Cela inclut la vérification NULL dans les langages non NULL tels que Java.

Exemple de vérification NULL en Java


if (hoge != null) {
    hoge.fuga();
}

Cependant, de tels gardiens risquent de réduire et de masquer les bogues, donc s'il existe un mécanisme d'assertion qui arrête le traitement lorsque la fraude se produit uniquement dans l'environnement de développement, il est préférable de l'utiliser à la place. Par exemple, la fonction ʻassert` de Swift ne peut être vérifiée que pour les conditions et plantée si elle n'est pas valide pour les constructions DEBUG.

Exemple d'assert dans Swift


func hoge(text: String?) {
    assert(text != nil) //DEBUG build plante ici si le texte est nul
    //Traitement normal
}

Ou, même si vous mettez une garde dans l'instruction if, il est préférable de générer au moins un journal si un état non valide se produit.

Évitez de vous écraser en premier lieu

Si possible, créez un environnement de développement dans lequel le code en panne est difficile à écrire en premier lieu. C'est une solution plus radicale que la mise en place de gardes. Par exemple, une solution fondamentale à NullPointerException consiste à "quitter Java et utiliser Kotlin NULL-safe".

Vous pouvez également ajouter des extensions pour écrire du code en toute sécurité, comme l'ajout d'une classe facultative à un langage qui n'est pas NULL-safe, sans avoir à changer de langue.

Ce qui suit est un exemple d'extension de Swift's Collection et d'ajout d'un getter (indice) qui renvoie nil sans planter même si un index en dehors de la plage est spécifié.

extension Collection {
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

let list = [0, 1]
print(list[safe: -1]) //Devenir nul sans s'écraser

Les plantages courants et courants tels que l'accès NULL et l'accès hors de portée d'un tableau peuvent être traités en ajoutant une telle fonction.

Faites autant que possible même s'il y a des données incorrectes

Même si une partie des données a un état invalide ou un état inattendu, la partie qui n'a pas de problème doit être traitée comme d'habitude autant que possible. Par exemple, considérons un système qui vous permet d'afficher les détails du compte bancaire.

En comparant un système dans lequel rien ne peut être vu si l'une des données du relevé d'utilisation est incorrecte et un système dans lequel le solde du compte peut être visualisé avec les autres détails même si l'une des données du relevé d'utilisation est incorrecte, ce dernier est naturellement plus pratique pour l'utilisateur.

Idéalement, l'application se comporte comme un bon majordome qui assiste l'utilisateur (du moins je pense). Il est loin d'être un bon diacre d'abandonner toutes ses fonctions par une seule chose inattendue.

** Cependant, cette politique s'applique uniquement à la possibilité d'afficher des informations. ** ** Dans le cas d'un traitement de mise à jour irréversible, il est prudent d'annuler le traitement dès qu'une anomalie est détectée.

[: Star: 3] N'écrivez pas encore et encore le même processus

En combinant les mêmes processus écrits à plusieurs endroits en un seul, il est possible de réduire la quantité de code, d'améliorer la lisibilité et de réduire le coût de la modification.

Découpez les fonctions générales en classes et fonctions communes

Les fonctions à usage général qui ne sont pas affectées par les exigences métier sont découpées et utilisées comme classes et fonctions communes. Par exemple, ce qui suit définit une fonction pour l'encodage URL d'une chaîne de caractères en tant que fonction commune.

extension String {
    var urlEncoded: String {
        var allowedCharacterSet = CharacterSet.alphanumerics
        allowedCharacterSet.insert(charactersIn: "-._~")
        return addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? ""
    }
}

Réduisez la description dans les instructions if et switch

Lors du branchement d'un processus avec une instruction if ou une instruction switch, si le même processus existe dans la branche, la partie dupliquée doit être retirée de l'instruction if ou de l'instruction switch. Par exemple, dans ce qui suit, la partie de label.text = est dupliquée à la fois dans le bloc if et dans le bloc else.

Bad


if flag {
    label.text = "aaa"
} else {
    label.text = "bbb"
}

Dans ce cas, l'instruction if elle-même peut être effacée en supprimant la partie dupliquée.

Good


label.text = flag ? "aaa" : "bbb"

Inconvénients de la communauté

La normalisation de la logique et des structures de données présente également des inconvénients. Le partage inutilement du même processus indistinctement conduit souvent à une diminution de la maintenabilité.

La standardisation du traitement présente les inconvénients suivants

En particulier, la similitude par l'héritage de classe crée un couplage fort et peut réduire la flexibilité du programme, alors soyez prudent.

Article associé En parlant de sommeil, "héritons pour rendre le code commun" L'idée que la communauté ne crée que des anti-modèles

Ce qui devrait et ne devrait pas être commun

Alors, quand devrait-il être courant et quand ne devrait-il pas l'être?

Comme mentionné au début, nous viserons activement à normaliser les fonctions à usage général qui ne sont pas affectées par les exigences de l'entreprise. Dans le cas de la logique et des données liées aux exigences de l'entreprise, c'est une décision au cas par cas de normaliser ou non, mais le respect de ce qui suit est une ligne directrice pour la normalisation.

[: Star: 2] Faites un croquis avant d'écrire du code

Tout comme vous créez d'abord des wireframes et des esquisses approximatives lorsque vous créez une application, les programmes peuvent procéder efficacement en créant des wireframes et des esquisses approximatives avant de commencer à écrire du code.

Lorsque le développement commence, nous voulons faire des progrès visibles, nous avons donc tendance à commencer à écrire du code pour le moment, mais le code écrit sans réfléchir profondément provoque souvent des retouches en raison de l'omission de considération, des spécifications inadéquates et une conception inadéquate. Si vous écrivez le code pour le moment, il semble qu'il progresse, mais le mauvais code est souvent ** totalement inutile, voire nuisible ** à la fin.

Par conséquent, il est plus efficace de supporter le désir d'écrire du code dans un premier temps et de faire une esquisse du programme du point de vue de l'ensemble du projet.

Qu'est-ce qu'une ébauche d'un programme?

L'esquisse approximative du programme mentionné ici suppose un diagramme de classes et un code hiérarchique. Cependant, ce n'est pas toujours le cas et un autre formulaire peut être utilisé si ce qui suit est connu.

Ecrire un diagramme de classe

Au début, il est facile de comprendre si vous créez un diagramme de classes pour prendre en compte les relations d'inclusion et de référence entre les classes. Le diagramme de classe à ce stade ne nécessite aucune règle et vous pouvez dessiner à votre guise. Il n'est pas nécessaire de couvrir les propriétés et les fonctions de la classe, et au moins le nom, la relation d'inclusion, la relation de référence et le rôle de la classe doivent être connus.

Il est recommandé de le faire à la main car il sera réécrit par essais et erreurs. Il est plus facile à comprendre si vous écrivez plusieurs côtés N d'une paire de N ou si vous entourez l'inclusion d'une ligne pour rendre le diagramme UML plus simple et plus intuitif.

Déterminez les données et l'interface que la classe contient

Une fois la structure de la classe décidée, l'étape suivante consiste à décider de l'interface entre les données détenues par chaque classe et la fonction requise pour la coopération avec l'extérieur.

Les données à conserver sont spécifiquement une variable d'instance (variable membre). Contrairement au diagramme de classes, les variables d'instance doivent être couvertes autant que possible à ce stade.

Comme mentionné dans la première section, les variables d'instance de classe doivent être aussi petites que possible. Transformez uniquement les variables nécessaires en variables d'instance et essayez de ne pas ajouter de variables d'instance non planifiées après la conception.

Écrire du code approximatif

Une fois la structure de classe décidée, écrivez un code approximatif sans détails. Cependant, puisque le but ici est de décider du plan, écrivez d'une manière qui vous est facile à comprendre sans être lié par une grammaire détaillée. Par conséquent, il est recommandé d'écrire dans un éditeur simple plutôt que dans un IDE qui vérifie les erreurs de compilation.

Dans le code approximatif, il suffit que le flux de traitement, la façon d'appeler d'autres classes et l'interface pour transmettre des informations soient approximativement déterminés.

Exemple de code approximatif


func createSession(request) -> Session? {
    let user = userTable.getByUserName(request.userName)

    if user.Compte bloqué{
jeter une erreur
    }

    if user.passward != request.password {
        userTable.Comptez le nombre d'échecs de connexion
        return nil
    }

Sortie de journal des informations utilisateur connecté
    return Session()
}

Utilisez l'interface pour transformer un code approximatif en code produit

L'interface vous permet d'implémenter la logique métier qui constitue l'épine dorsale de l'implémentation détaillée et des problèmes en attente. En d'autres termes, il peut être compilé et un code approximatif peut être créé afin qu'il puisse être utilisé dans le produit tel quel. Cette méthode est très efficace pour les programmes backend avec des conteneurs DI.

Ce qui suit est un exemple de code approximatif (le langage est Java) qui utilise l'interface avec Spring Boot qui a un conteneur DI.

Ce programme télécharge des photos sur un serveur de fichiers et envoie par e-mail l'URL du fichier à l'adresse e-mail de l'utilisateur obtenue à partir de la base de données. En utilisant l'interface, vous pouvez implémenter la logique métier sans décider de la base de données, du serveur de fichiers, du serveur de messagerie ou de la bibliothèque à utiliser.

Exemple d'utilisation de l'interface Java


@Service
class FileService {
    private final IMailer mailer;
    private final IUserTable userTable;
    private final IFileUploader fileUploader;

    //Dans Spring, la valeur est automatiquement définie à partir du conteneur DI vers l'argument du constructeur.
    public FileService(IMailer mailer, IUserTable userTable, IFileUploader fileUploader) {
        this.mailer = mailer;
        this.userTable = userTable;
        this.fileUploader = fileUploader;
    }

    void uploadUserPhoto(int userId, byte[] photoData) {
        String fileURL = fileUploader.upload(userId, photoData);
        User user = userTable.get(userId);
        mailer.send(user.emailAddress, "Téléchargement terminé", fileURL);
    }
}

interface IMailer {
    void send(String toAddress, String title, String body);
}
interface IUserTable {
    User get(int userId);
}
interface IFileUploader {
    String upload(int userId, byte[] fileData);
}

Si vous créez un prototype de système en mettant une implémentation simulée simple dans une telle interface, vous pouvez développer de manière flexible et rapide en ignorant la base de données et le serveur pour le moment.

Trouver des lacunes dans les spécifications

Trouver des lacunes dans le cahier des charges est également l'un des objectifs de la réalisation d'une ébauche.

Lors de la rédaction d'un programme basé sur des spécifications et des documents de conception, il est nécessaire de garder à l'esprit que ** les spécifications et les documents de conception sont toujours incorrects **. Si vous remarquez une erreur dans les spécifications après la mise en œuvre, le travail jusqu'à ce point sera gaspillé.

Par conséquent, lors de la rédaction d'un croquis approximatif, sachez qu'il n'y a pas de lacunes dans les spécifications.

[: Star: 2] Gardez les objets associés à proximité

Placer le code associé dans le même répertoire ou dans le même fichier vous évite de trouver et de changer de fichier lors de la lecture du code. Par exemple, placez les fichiers utilisés par la même fonction dans le même répertoire, ou faites d'une classe utilisée uniquement dans une classe spécifique une classe interne, et placez le code associé à proximité pour faciliter la recherche du code.

Diviser les répertoires par fonction ou par type

Lors du regroupement de fichiers dans des répertoires et des packages, il existe deux méthodes, l'une consiste à les regrouper par fonction et l'autre à les regrouper par type de fichier.

  1. Exemple de regroupement par type de fichier
Storyboard
├ LoginViewController.storyboard
└ TimeLineViewController.storyboard

View
├ LoginView.swift
└ TimeLineView.swift

Controller
├ LoginViewController.swift
└ TimeLineViewController.swift

Presenter
├ LoginViewPresenter.swift
└ TimeLineViewPresenter.swift

UseCase
├ LoginViewUseCase.swift
└ TimeLineViewUseCase.swift
  1. Exemple de résumé par fonction
Login
├ LoginViewController.storyboard
├ LoginView.swift
├ LoginViewController.swift
├ LoginViewPresenter.swift
└ LoginViewUseCase.swift

TimeLine
├ TimeLineView.swift
├ TimeLineViewController.storyboard
├ TimeLineViewController.swift
├ TimeLineViewPresenter.swift
└ TimeLineViewUseCase.swift

Les deux exemples ci-dessus ont les mêmes fichiers, mais avec des divisions de répertoire différentes.

La méthode de regroupement par type de fichier est facile à comprendre la conception de la couche, et les fichiers utilisés par plusieurs fonctions peuvent être organisés sans cohérence, mais c'est compliqué car il est nécessaire de rechercher des fichiers dans 5 répertoires afin de lire le code sur un écran. Il y a aussi des inconvénients.

Ce n'est pas la meilleure façon de diviser ces deux méthodes, et il est nécessaire de les utiliser correctement en fonction de la situation, mais si le groupe de fichiers n'est pas utilisé par d'autres fonctions, il peut être plus facile à développer en regroupant les répertoires selon la fonction. Il y a beaucoup de.

Équilibre entre couplage lâche et serré

En général, les programmes faiblement couplés sont plus polyvalents et maintenables, mais la politique de cette section augmente inversement le degré de couplage de programmes. Si le degré de couplage est trop élevé, une lime deviendra trop grande ou trop étroitement couplée, ce qui nuira à la polyvalence et à la maintenabilité, mais un couplage lâche inutile et excessif présente également l'inconvénient que le coût l'emportera sur les avantages. ..

Ce qui est le mieux, c'est une question qui n'a pas de réponse correcte au cas par cas, mais il faut être conscient de mettre le code ensemble dans un équilibre approprié.

[: Star: 2] Créer un test unitaire

UnitTest ici n'est pas un test manuel, mais un test de programme tel que JUnit. Créez activement UnitTest dès le début du développement pour de petites fonctions telles que le traitement de données.

Avantages de UnitTest

Réduisez le coût global en vérifiant à l'avance

Si un problème est détecté dans le système, plus la fonction est volumineuse, plus il faut de temps pour rechercher la cause. De plus, plus le processus est tardif, plus l'influence de la modification du programme sur les autres fonctions et les points à prendre en compte est grande. Ces coûts peuvent être réduits en vérifiant le fonctionnement des petites fonctions avec UnitTest à un stade précoce.

Vérifier le fonctionnement des processus difficiles à déplacer et tester

Le traitement sur des données et des états irréguliers est souvent difficile à tester en exécutant réellement l'application. Même dans de tels tests, UnitTest vous permet de créer par programmation des situations irrégulières et de les tester.

Vérifier l'utilisabilité des classes et des fonctions

En écrivant UnitTest, le programmeur utilisera en fait la classe ou la fonction testée dans le programme. En faisant l'expérience de la convivialité des classes et des fonctions à ce stade, vous pouvez rafraîchir l'interface des classes et des fonctions sous une forme plus conviviale. En outre, comme effet secondaire, exécuter UnitTest nécessite que le programme soit faiblement couplé, ce qui peut être un bon moyen d'étudier la conception propre.

Clarifier les spécifications

Vous devez déterminer ce qui est juste pour faire un test. En considérant le test, les spécifications et les questions telles que ce qui doit être fait dans le cas irrégulier deviennent plus claires.

UnitTest est fait pour accélérer le développement

UnitTest n'est pas fait pour garantir la qualité, mais pour accélérer le développement global.

Par conséquent, il n'est pas nécessaire de couvrir tout le code et il n'est pas nécessaire de forcer un test difficile à mettre en œuvre. En particulier, lorsque l'interface utilisateur, la communication, la base de données, etc. sont impliqués, UnitTest doit être pris en compte de manière variée, il existe donc un risque que l'efficacité du développement diminue si un tel UnitTest est créé.

De plus, vous devriez arrêter de penser que la qualité est garantie parce que vous avez fait UnitTest. Peu importe la quantité de UnitTest que vous faites, vous devrez éventuellement faire un test manuel.

Testez de petites fonctionnalités

UnitTest est essentiellement fait pour des fonctions petites et indépendantes. Parfois, vous créez un programme qui vérifie automatiquement une grande série d'opérations, mais qui a un objectif différent de UnitTest.

[: Star: 2] Utilisez des classes numériques pour calculer la logique métier

N'utilisez pas de types de données de base tels que Int, Float et Double pour les calculs de logique métier, mais utilisez des classes numériques telles que BigDecimal pour Java et NSDecimalNumber et Decimal pour Swift.

Les nombres à virgule flottante tels que Double et Float ne sont fondamentalement pas utilisés pour la logique métier

Les nombres à virgule flottante tels que Double et Float ne doivent pas être utilisés facilement car ils provoquent des erreurs. Les points flottants ne sont généralement utilisés que pour le traitement des dessins, comme le calcul de coordonnées et le calcul scientifique qui met l'accent sur les performances. Si vous faites une erreur, n'utilisez pas Double ou Float pour calculer le montant.

N'utilisez pas 32 bits Int pour calculer le montant

Par exemple, Java Int est 32 bits et la valeur maximale est d'environ 2,1 milliards, ce qui est trop petit pour gérer le montant d'argent. 2,1 milliards de yens, c'est beaucoup d'argent, mais dans le cas de la comptabilité au niveau de l'entreprise, le montant qui dépasse ce montant est normal, et ce n'est pas un montant qui ne peut pas être un actif personnel.

Étant donné que les types d'entiers intégrés tels que Int ont une limite supérieure sur le nombre de chiffres, évitez autant que possible de les utiliser dans la logique métier et, lorsque vous les utilisez, examinez attentivement s'il existe des cas où les chiffres débordent. Pour les montants monétaires, l'utilisation d'une valeur entière de 64 bits (type Long pour Java) devrait être presque suffisante. La valeur maximale d'une valeur entière de 64 bits est d'environ 900 K, donc même 100 fois le budget national américain (environ 400 billions de yens) peuvent être facilement pris en charge.

Article associé Pourquoi devrais-je utiliser BigDecimal

[: Star: 2] N'augmentez pas trop le nombre de classes et d'interfaces

Vous devriez essayer de ne pas rendre la classe trop grande, mais il y a les inconvénients suivants à diviser excessivement la classe et à augmenter trop le nombre de classes et d'interfaces.

Lors de la conception d'une structure de classe ou de couche, il est nécessaire de prendre en compte ces inconvénients et de concevoir les avantages l'emportent sur les inconvénients.

Pointez pour couper la classe

Lors de la découpe d'une fonction dans une classe, la fonction est probablement l'une des suivantes.

Au contraire, il est fort possible qu'il soit préférable de fournir un groupe de fonctions qui ne sont pas réutilisables et qui ne sont pas testées ou déployées indépendamment en une seule classe sans les diviser en classes. Les fonctions de couplage lâche aident à maintenir la maintenabilité du système, mais il existe des coûts et des inconvénients, et un couplage lâche n'est pas toujours supérieur à un couplage serré.

Par exemple, dans les applications iOS, il existe un modèle de conception qui crée un ViewController et uneView personnalisée pour un écran spécifique, mais ces ViewController et View sont toujours liés un à un et ne sont pas réutilisables. Comme il n'est pas testé ou déployé indépendamment, il est souvent préférable de le combiner en une seule classe au lieu de le diviser en View et ViewController.

N'imitez pas mécaniquement les modèles de conception célèbres

Une subdivision excessive des classes est souvent le résultat d'une imitation mécanique de modèles de conception bien connus tels que DDD et l'architecture propre. Les livres et articles qui introduisent des modèles de conception ont tendance à mettre l'accent uniquement sur les bons points des modèles de conception et non sur les mauvais points ou les points gênants. La conception optimale dépend de la taille du produit et de l'équipe, donc au lieu d'imiter mécaniquement le modèle de conception existant, il suffit de l'appliquer à notre produit et de voir si les avantages l'emportent sur les inconvénients. considérer. Cependant, il est difficile de comprendre les inconvénients d'un modèle de conception particulier sans l'essayer une fois. Les modèles de conception optimaux doivent être progressivement affinés grâce à un cycle PDCA d'examen, d'exécution, de rétroaction et d'amélioration.

N'appliquez pas la même structure de couche uniformément sur l'ensemble de l'application

Par exemple, il est inutile d'appliquer de nombreuses classes et architectures complexes à un programme qui ne produit que Hello World. Il n'y a pas grand chose à faire, donc il y a des classes et des couches qui ne font rien.

Le type de classe ou de couche approprié dépend de la nature et de la complexité de la fonction à implémenter. ** Il n'y a pas de structure de classe ou d'architecture pratique qui correspond à quoi que ce soit. ** **

Une application typique a plusieurs écrans et fonctions, dont chacun a des propriétés et une complexité différentes, donc si la même structure de couche est appliquée à tous, des déchets et une discordance se produiront. Par conséquent, au lieu d'appliquer la même configuration à toutes les fonctions, il vaut mieux être plus flexible et sélectionner une conception appropriée pour chaque fonction.

Même si les fonctions sont les mêmes, la meilleure configuration changera à mesure que les exigences et les spécifications changent. Une refactorisation audacieuse et même l'abandon total du code existant sont parfois nécessaires pour maintenir une bonne conception.

Raccourcis de calque

Une façon de réduire le nombre de fichiers dans une architecture en couches, telle qu'une architecture propre, est d'autoriser les raccourcis de couche. Par exemple, même si le programme a la structure de couches suivante, s'il n'y a rien à voir avec UseCase et Presenter, le contrôleur peut se référer directement à l'entité.

Comme mentionné dans la section précédente, il n'est pas nécessaire d'unifier la structure des couches au sein de l'application, et je pense que vous pouvez la modifier plus librement en fonction de la fonction. Cependant, comme décrit dans la section «Conscient de la direction de dépendance», ne vous référez pas à la vue de l'entité dans la direction opposée.

Remplacer l'interface (protocole) par la fermeture

Si le langage peut passer des fonctions comme arguments, comme un langage de type fonction, une interface simple (protocole) qui n'a qu'une seule méthode peut être remplacée par une fonction passant ou fermée. Ce qui suit remplace la sortie du présentateur du protocole au passage de fonction.

Before


class LoginPresenter {
    let output: LoginOutput
    init(output: LoginOutput) {
        self.output = output
    }
    func login() {
        output.loginCompleted()
    }
}
protocol LoginOutput {
    func loginCompleted()
}

After


class LoginPresenter {
    func login(onComplete: () -> Void) {
        onComplete()
    }
}

Ne créez pas plusieurs modèles de données similaires

Si vous êtes trop obsédé par la séparation des couches, vous risquez de vous retrouver avec plusieurs modèles de données similaires sans signification. Par exemple, il y a une classe ʻUser dans la couche de domaine, un ʻUserDTO pour transmettre ces informations à UseCase, et un ʻUserViewModel` à utiliser dans View, mais les trois ont presque le même code C'est une affaire. Dans certains cas, même s'ils sont presque identiques, ils doivent être séparés, mais dans d'autres cas, ils peuvent être réutilisés sans être séparés. Si les conditions suivantes sont remplies, le modèle de données supérieur peut être utilisé sur plusieurs couches.

[: Star: 2] Attrapez l'exception et ne la pressez pas

Fondamentalement, Exception est renvoyée à l'appelant sans interception et la gestion des erreurs est effectuée collectivement dans la couche supérieure. Lorsque vous détectez une exception, gardez à l'esprit les points suivants.

Le contenu de cette section est un peu incompatible avec ce qui est écrit dans la section "Conscient de la disponibilité des services", mais il est préférable que les couches supérieures puissent gérer correctement les exceptions.

La politique décrite dans la section «Conscience de la disponibilité du service» consiste à éviter de lancer des exceptions si les couches supérieures ne sont pas en mesure de gérer correctement les exceptions et que cela entraîne une panne à l'échelle du système. Chose.

N'utilisez pas d'exceptions pour la logique de branchement

Même si l'exception est gérée dans la couche supérieure, arrêtons d'utiliser l'exception pour le branchement conditionnel comme l'instruction if dans le flux de contrôle normal. Les exceptions ne sont que des exceptions et vous devriez essayer de ne pas les utiliser sauf dans des situations exceptionnelles.

Comment rendre les exceptions de vérification Java (vérifier les exceptions) silencieuses

L'exception standard Java devient une exception vérifiée (exception vérifiée) et throw force la fonction appelante à intercepter ou à renvoyer. Par conséquent, si vous essayez de gérer une exception au niveau de la couche supérieure, vous devrez ajouter throws à l'expression qui appelle l'appelant de la fonction dans laquelle l'exception se produit.

Parfois c'est ennuyeux, mais si vous ne voulez pas attraper et masquer l'exception, vous pouvez l'envelopper dans une RuntimeException et la lancer sans apporter de modifications à l'appelant.

try {
    "ABC".getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
    //IllegalStateException est une RuntimeException donc ne forcez pas la capture en haut
    throw new IllegalStateException(e);
}

[: Star: 1] Rendre les variables et les fonctions qui ne sont pas référencées de l'extérieur comme privées

Rendre les propriétés et les fonctions qui ne sont pas utilisées de l'extérieur de la classe comme privées ou protégées.

Il est souhaitable de comprendre comment utiliser une classe par complétion de code sans regarder le code ou la documentation, mais si vous ne la rendez pas privée ou protégée, les propriétés et les fonctions qui ne sont pas censées être utilisées de l'extérieur apparaîtront également comme candidats dans la complétion de code, ce qui le rendra difficile à utiliser. ..

Cependant, certains langages de programmation n'ont pas de modificateurs d'accès tels que private, auquel cas il vaut mieux abandonner private plutôt que d'implémenter private avec une implémentation spéciale, en donnant la priorité au code simple et standard. Si vous voulez vraiment introduire private, il est recommandé de changer la langue en premier lieu (comme passer de JavaScript à TypeScript)

De plus, étant donné que la méthode privée est souvent mise en œuvre proprement en coupant le traitement vers une méthode publique d'une autre classe, s'il s'agit d'un traitement à usage général ou si la taille de la classe est grande, envisagez de séparer les classes.

Article associé Aucune méthode privée requise

[: Star: 1] N'incorporez pas de descriptions compliquées dans les opérateurs ternaires

L'imbrication d'opérateurs ternaires prête à confusion et doit être évitée.

Bad


flag ? subFlag ? a : b : c

Lorsqu'une imbrication est requise, il est conseillé de placer le résultat de l'opération au milieu dans une variable locale (variable explicative) pour éliminer l'imbrication.

Good


let aOrB = subFlag ? a : b
flag ? aOrB : c

De plus, lorsqu'une expression longue ou une chaîne de fonctions longues est incluse dans l'opérateur ternaire, placez chaque résultat dans une variable locale puis utilisez-la dans l'opérateur ternaire.

Bad


flag ? (1 + 2) * 3 - 4 : 5 % 6 * 7

Good


let a = (1 + 2) * 3 - 4
let b = 5 % 6 * 7
flag ? a : b

Article associé L'opérateur ternaire?: Est-ce le mal.

[: Star: 1] N'écrivez pas solidement vrai / faux

Les débutants ont tendance à écrire «vrai» faux »dans leur code comme suit.

Bad


var isZero: Bool {
   if number == 0 {
      return true
   } else {
      return false
   }
}

Cependant, dans ce qui précède, puisque number == 0 renvoie Bool, il n'est pas nécessaire d'écrire vrai / faux, et cela peut être écrit de manière plus concise comme suit.

Good


var isZero: Bool {
   return number == 0
}

Dans les cas où true est toujours retourné ou dans les cas où false est toujours retourné, il est nécessaire d'écrire vrai / faux en solide, mais lorsque vous retournez un résultat de jugement comme dans l'exemple ci-dessus, évitez d'écrire vrai / faux en solide.

Cependant, cette méthode d'écriture est difficile à comprendre pour les débutants. Si vous avez un débutant dans votre équipe, c'est une bonne idée de l'expliquer.

Article associé Je souhaite rédiger une instruction if conviviale

[: Star: 1] N'utilisez pas la valeur de code de enum

Comme enum rawValue de Swift, enum a souvent une valeur de code. Cependant, si cette valeur de code est utilisée pour le jugement, la signification de l'existence d'énumération sera réduite de moitié.

Échantillon d'énumération


enum Status: String {
   case success = "0"
   case error = "1"
}

Par exemple, s'il existe un Enum avec une valeur de code comme décrit ci-dessus, utilisez directement Enum au lieu d'utiliser la valeur de code pour le jugement.

Bad


if status.rawValue == "0" {
}

Good


if status == .success {
}

Même lors du passage en argument, la valeur de code n'est pas utilisée et Enum est passé directement.

Bad


checkStatus(status.rawValue)

Good


checkStatus(status)

Enum ne devrait-il pas avoir une valeur de code en premier lieu?

Ceci est incompatible avec la section «Définir la valeur de code sur enum», mais si enum n'a pas de valeur de code en premier lieu, une telle erreur ne se produit pas.

Étant donné que les valeurs de code de DB et API sont des spécifications externes et doivent être séparées des spécifications de l'application, il est préférable de convertir les valeurs de code en enum dans la couche Repository et de ne pas donner les valeurs de code à enum. devenir.

[: Star: 1] Utiliser l'outil de vérification de code statique

Utilisez activement les contrôles de code statiques tels que Lint.

La vérification statique du code vous permet de vérifier le code à faible coût sans intervention humaine, il est donc préférable de le faire. Non seulement vous pouvez détecter les problèmes, mais vous pouvez également savoir quel type de code pose un problème.

Cependant, la vérification de code statique a également ses inconvénients, et cela peut prendre beaucoup de temps pour tout vérifier sérieusement lorsqu'un grand nombre d'avertissements sont émis. Ce n'est pas parce qu'un avertissement est émis qu'il y a un problème immédiat, il est donc possible que la qualité de l'application ne change pas beaucoup même si l'avertissement est écrasé avec le temps.

Il peut être préférable de penser à la vérification de code statique uniquement pour aider à trouver des problèmes.

[: Star: 0] Autres pratiques diverses

Je n'entrerai pas dans les détails, mais j'énumérerai d'autres pratiques diverses.

: information_source: informations sur la tâche

Qu'as-tu pensé? Cet article est la directive de codage de Altnotes Co., Ltd., que je représente.

Nous sommes une entreprise de 2 personnes dont le représentant, mais nous recherchons actuellement des ingénieurs pour iOS / Android. Si vous êtes intéressé, veuillez consulter les informations sur l'emploi!

Alto Notes Job Information

Recommended Posts

Comment écrire du bon code
Comment rédiger un code facile à comprendre [Résumé 3]
Comment écrire des rails
Comment écrire docker-compose
Comment écrire Mockito
Comment écrire un fichier de migration
Comment écrire du code qui pense Ruby orienté objet
Comment écrire du code de test avec la certification de base
Comment rédiger un commentaire java
[Refactoring] Comment écrire le routage
Comment écrire Junit 5 organisé
Comment écrire des graines de Rails
Comment écrire le routage Rails
Étudier Java # 6 (Comment écrire des blocs)
[Rails] Comment écrire la gestion des exceptions?
Comment écrire une déclaration de variable Java
[Basique] Comment écrire un auto-apprentissage Dockerfile ②
[R Spec on Rails] Comment écrire du code de test pour les débutants par les débutants
Résumé de l'écriture des arguments d'annotation
[Introduction à Java] Comment écrire un programme Java
Comment appeler le code Swift 5.3 depuis Objective-C
[Java] Comment sortir et écrire des fichiers!
Écrire du code difficile à tester
Comment écrire un spécificateur de coupe de point Spring AOP
[SpringBoot] Comment écrire un test de contrôleur
Comment compter rapidement les points de code UTF-8
AtCoder s'appelle TLE et explique comment écrire du beau code
Promesse JDBC et exemple d'écriture
Rails: comment bien écrire une tâche de râteau
[Java FX] Comment écrire des autorisations Eclipse dans build.gradle
[Rails] Comment écrire lors de la création d'une sous-requête
Écrire du code facile à maintenir (partie 1)
Bases du développement Java ~ Comment écrire des programmes * Exercice 1 ~
Comment colorer la sortie de la console de code dans Eclipse
Comment écrire une instruction if pour améliorer la lisibilité-java
JUnit 5: Comment écrire des cas de test dans enum
Exemple d'implémentation de F06 d'écriture en temps réel hors ligne
Écrire du code facile à maintenir (partie 4)
Comment écrire React Native Bridge ~ Version Android ~
[Java] Mémo sur la façon d'écrire la source
Comment écrire Java String # getBytes dans Kotlin?
Notes sur la façon de rédiger des commentaires en anglais
Écrire du code facile à maintenir (partie 3)
Comment développer OpenSPIFe
Comment appeler AmazonSQSAsync
Comment utiliser rbenv