Swift.assert - la vie après la libération

À quelle fréquence utilisez-vous Swift.assert() dans votre code? Je l'utilise honnêtement assez souvent (si c'est une mauvaise pratique, alors veuillez écrire dans les commentaires - pourquoi est-ce mauvais?). Dans mon code, vous pouvez souvent voir, par exemple, un tel appel:

 Swift.assert(Thread.isMainThread) 

Il n'y a pas si longtemps, j'ai décidé que ce serait bien de continuer à observer les résultats de ces appels, non seulement dans le cadre du lancement de l'application dans le simulateur / appareil, mais aussi à partir des actions de vrais utilisateurs. Au fait, ici, nous pouvons parler de Swift.precondition() , Swift.fatalError() , etc., bien que j'essaie de les éviter. J'ai lu plus d'informations sur les erreurs irrécupérables dans Swift dans cette publication et cela s'est avéré très instructif.

Plus près du but: dans le code, j'ai trouvé environ 300 de ces appels. Aucun d'eux n'a travaillé pendant les tests locaux habituels, et cela m'a fait plaisir. Mais j'ai suggéré que les actions des utilisateurs pourraient toujours déclencher certains appels.

Du point de vue de l'utilisateur, les plantages ne devraient pas se produire (encore une fois, à mon avis). Dans les cas extrêmes, l'utilisateur doit comprendre qu'un scénario s'est mal passé et que l'équipe de développement travaille déjà à le corriger. Des exceptions similaires ont toujours été gérées par moi, et pour l'utilisateur, cela pourrait affecter sous la forme la plus inoffensive. Par exemple, l'une des centaines de cellules du tableau était tout simplement invisible.

Avec l'utilisateur plus ou moins, tout est clair. Il reste à gérer la livraison des logs au développeur. Premièrement, il était nécessaire avec un minimum d'effort de remplacer les appels en cours dans le code par des appels envoyant des journaux quelque part en dehors de l'application. Deuxièmement, il était nécessaire de localiser avec précision la scène de l'incident, sinon il serait pratiquement impossible de corréler l'exception avec le code réel. Troisièmement, il convient de noter que les appels modifiés peuvent fonctionner pendant les tests unitaires, où Thread.isMainThread doit déjà être ignoré. J'utilise le framework RxTest pour certains types de tests (ici, je suis également prêt à écouter les conseils et les critiques). Le point principal restait que localement toutes les exceptions devraient être déclenchées comme auparavant, c'est-à-dire Loggin.assert() devrait se déclencher en même temps que Swift.assert() se déclencherait

Fabric ( Crashlytics ) fournit un excellent moyen d'envoyer des événements. Cela ressemble à ceci:

 Crashlytics.sharedInstance().recordCustomExceptionName("", reason: ""... 

Il reste à emballer Crashlytics dans un cadre qui peut être chargé dans son intégralité dans l'application et sous une forme tronquée (sans dépendance Crashlytics ) dans des cibles de test.

J'ai décidé de faire le « packaging » via CocoaPods :

 Pod::Spec.new do |s| s.name = 'Logging' ... s.subspec 'Base' do |ss| ss.source_files = 'Source/Logging+Base.swift' ss.dependency 'Crashlytics' end s.subspec 'Test' do |ss| ss.source_files = 'Source/Logging+Test.swift' end end 

Le code de la "cible de combat" est le suivant:

 import Crashlytics public enum Logging { public static func send(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { let file = __file.components(separatedBy: "/").last ?? __file let line = "\(__line)" let name = [line, file].joined(separator: "_") Crashlytics.sharedInstance().recordCustomExceptionName(name, reason: reason ?? "no reason", frameArray: []) } public static func assert(_ assertion: @escaping @autoclosure () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { if assertion() == false { self.assertionFailure(reason, __file: __file, __line: __line) } } public static func assert(_ assertion: @escaping () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { if assertion() == false { self.assertionFailure(reason, __file: __file, __line: __line) } } public static func assertionFailure(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { Swift.assertionFailure(reason ?? "") self.send(reason, __file: __file, __line: __line) } } 

Pour la "cible de test", c'est-à-dire sans dépendance Crashlytics :

 import Foundation public enum Logging { public static func send(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { // } public static func assert(_ assertion: @escaping @autoclosure () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { // } public static func assert(_ assertion: @escaping () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { // } public static func assertionFailure(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { // } } 

Résultats:


Les exceptions ont vraiment commencé à fonctionner. La plupart d'entre eux ont signalé un format incorrect pour les données reçues: Decodable recevait parfois des données de Decodable type. Parfois, les journaux de Thread.isMainThread fonctionnaient, qui ont été très rapidement corrigés dans les prochaines versions. Les erreurs les plus intéressantes ont été miraculeusement détectées par NSException .

Merci de votre attention.

PS Si vous envoyez trop souvent de tels journaux à Crashlytics , le service peut reconnaître vos actions comme du spam. Et vous verrez le message suivant:
En raison d'une mauvaise utilisation, les rapports non fatals ont été désactivés pour plusieurs versions. Découvrez comment réactiver les rapports dans notre documentation.
Par conséquent, il convient de considérer à l'avance la fréquence d'envoi des journaux. Sinon, tous les journaux de génération risquent d'être ignorés par Crashlytics.

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


All Articles