DEV Community

nikosst
nikosst

Posted on

Γιατί το string.Empty είναι πιο ειλικρινής αρχικοποίηση από το null! στα DTO

Στη C#, δύο τρόποι αρχικοποίησης μιας ιδιότητας μπορεί να φαίνονται ισοδύναμοι:

public string Name { get; init; } = null!;
public string Name { get; init; } = string.Empty;

Enter fullscreen mode Exit fullscreen mode

Και οι δύο αποφεύγουν την προειδοποίηση για μη αρχικοποιημένη non-nullable ιδιότητα. Και στις δύο περιπτώσεις, το FluentValidation μπορεί να απορρίψει την αρχική τιμή.

Όμως δημιουργούν διαφορετική κατάσταση.

Το null! αναθέτει πραγματικό null. Το ! απλώς καταστέλλει την προειδοποίηση του compiler. Δεν προσθέτει προστασία κατά την εκτέλεση ούτε εγγυάται ότι η τιμή θα συμπληρωθεί αργότερα.

Microsoft: null-forgiving operator.

Το string.Empty αναθέτει "": μια πραγματική συμβολοσειρά χωρίς χαρακτήρες. Δεν είναι το ίδιο με το " ", που περιέχει ένα κενό.

Microsoft: String.Empty.

Για ένα DTO όπου η απουσία και το κενό αντιμετωπίζονται ισοδύναμα, προτιμώ το string.Empty: η αρχική τιμή συμφωνεί καλύτερα με την πρόθεση της δήλωσης string.

Έχουμε κείμενο. Απομένει να ελέγξουμε αν έχει αποδεκτό περιεχόμενο.

Το FluentValidation θα τα πιάσει και τα δύο:

RuleFor(x => x.Name)
    .NotEmpty()
    .WithMessage("Το όνομα είναι υποχρεωτικό.");
Enter fullscreen mode Exit fullscreen mode

Για συμβολοσειρές, το NotEmpty() απορρίπτει:
Τιμή Αποτέλεσμα
null Αποτυχία
"" Αποτυχία
" " Αποτυχία
"Μαρία" Επιτυχία

Αυτό επιβεβαιώνεται από την τεκμηρίωση του FluentValidation και τον πηγαίο κώδικα του validator.

Η προτίμηση προς το string.Empty αφορά επομένως τη σαφήνεια της αρχικής κατάστασης. Διαχωρίζει δύο ευθύνες:

  • Αρχικοποίηση: το πεδίο ξεκινά με πραγματική συμβολοσειρά.
  • Validation: ελέγχουμε αν το περιεχόμενο επαρκεί για τη συγκεκριμένη λειτουργία.

Το validation δεν συμπληρώνει το όνομα. Αναφέρει το πρόβλημα ώστε η εφαρμογή να απορρίψει το αίτημα.

Σε ASP.NET Core με Clean Architecture, μια ενδεικτική ροή είναι:

API → DTO → Validation στο Application → Domain → Αποθήκευση

Το Application εκτελεί τον έλεγχο πριν συνεχίσει. Το API επιστρέφει κατανοητά σφάλματα, ενώ το Domain προστατεύει τους δικούς του απαραίτητους κανόνες. Η Clean Architecture υποστηρίζει αυτόν τον διαχωρισμό, χωρίς να επιβάλλει συγκεκριμένο initializer.

Microsoft: Clean Architecture.

Tο όφελος είναι η ευκολότερη κατανόηση: ο developer βλέπει τι περιέχει αρχικά το αντικείμενο και πού απορρίπτονται τα ελλιπή δεδομένα.

Υπάρχουν όμως όρια.

Το string.Empty ορίζει μόνο την αρχική τιμή. Ανάλογα με τις ρυθμίσεις του serializer, ένα εισερχόμενο JSON με "name": null μπορεί να την αντικαταστήσει. Ο έλεγχος της εξωτερικής εισόδου παραμένει απαραίτητος. Microsoft: nullable annotations στο System.Text.Json.

Επίσης, όταν η απουσία τιμής έχει ξεχωριστή σημασία, το string? μπορεί να περιγράφει καλύτερα το request:

public string? Name { get; init; }

Το όνομα μπορεί να είναι επιχειρησιακά υποχρεωτικό, παρότι ένα μη ελεγμένο αίτημα ενδέχεται να μην το περιέχει.

Υπάρχει και σχετική συζήτηση στα τεχνικά blogs. Ο Maarten Balliauw αποθαρρύνει την απόκρυψη του προβλήματος με default! και εξετάζει εναλλακτικές. Ο Ian Griffiths προειδοποιεί ότι και το κενό string μπορεί να κρύψει ελλιπή δεδομένα, όταν χρησιμοποιείται μόνο για να εξαφανιστεί μια προειδοποίηση.


Να θυμάσαι..!

Σε input DTOs όπου απουσία και κενό έχουν την ίδια σημασία, χρησιμοποιούμε string.Empty και υποχρεωτικό NotEmpty(). Όταν η απουσία έχει σημασία, τη μοντελοποιούμε ρητά. Κάθε null! χρειάζεται συγκεκριμένο και τεκμηριωμένο λόγο.

Εσείς πώς το αντιμετωπίζετε στα δικά σας projects; Προτιμάτε string.Empty, null! ή κάποια άλλη προσέγγιση για τα input DTOs; Θα ήθελα να ακούσω και διαφορετικές απόψεις, μαζί με το σκεπτικό και παραδείγματα από την εμπειρία σας. Σε ποιες περιπτώσεις θεωρείτε ότι μια άλλη επιλογή ταιριάζει καλύτερα;


Δείτε ακόμα Properties, getters/setters, και init, αρχικοποίηση πεδίων, διαφορά DTO vs EF Entities

[email protected]

Top comments (0)