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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 /)
--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.
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.
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.
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.
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.
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)
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-") }
}
}
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.
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.
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.
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.
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.
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.
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.
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
}
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.
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.
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.
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.
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.
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.
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.
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.
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
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 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.
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
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.
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.
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()
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.
** 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)
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.
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.
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.
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.
À 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.
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.
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
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.
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
}
}
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.
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)
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.
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
}
}
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.
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
}
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é.
À 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 /)
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.
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é)
}
É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.
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.
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
}
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.
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.
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.
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.
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
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.
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 changez
HTTPConnector 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)
}
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.
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
}
}
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)")
}
}
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.
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.
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()
}
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"
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.
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.
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.
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
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.
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.
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.
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.
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.
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) ?? ""
}
}
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"
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
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.
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.
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.
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.
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.
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()
}
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 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.
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.
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.
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
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.
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é.
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.
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.
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.
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.
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 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.
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.
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 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.
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
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.
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.
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.
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.
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.
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()
}
}
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.
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.
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.
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);
}
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
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.
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
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)
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.
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.
Je n'entrerai pas dans les détails, mais j'énumérerai d'autres pratiques diverses.
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!
Recommended Posts