Skip to content

Commit db9f631

Browse files
committed
Add analysis of constructors and field initialization.
This is part of investigating "Enhanced Default Constructors" or other possible data-class-like language features to make it easier to initialize instance state.
1 parent ccef473 commit db9f631

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
Users have long asked for a lighter notation to define a class with some fields
2+
and a constructor that initializes them. Sometimes, they ask for full "data
3+
classes", meaning that's basically *all* the class contains. But often they
4+
simply want a simpler syntax for declaring fields and their constructor
5+
initializers on a regular user-defined class.
6+
7+
The challenge is that there are many many things that field declarations,
8+
constructors, and constructor parameters may need to specify:
9+
10+
* Is the constructor `const` or not? Is it named?
11+
* Is there a `super()` initializer? Initializer list?
12+
* Is there a constructor body?
13+
* Is the field `final` or mutable? `late`? Does it `@override` a supertype
14+
getter?
15+
* Is the parameter named or positional, required or optional? Does it have a
16+
default value?
17+
18+
## Corpus analysis
19+
20+
It's unlikely that any syntactic sugar can cover *all* of these cases, so to
21+
make some patterns look better, we need to optimize for the most common ones. To
22+
that end, I scraped a corpus of 2,000 pub packages (7,116,622 lines in 51,038
23+
files) to look at how they define and initialize their fields:
24+
25+
```
26+
-- Constructor type (69447 total) --
27+
39003 ( 56.162%): non-const ========================
28+
16254 ( 23.405%): non-const factory ==========
29+
13341 ( 19.210%): const ========
30+
849 ( 1.223%): const factory =
31+
```
32+
33+
Most constructors are not `const`, but `const` and `factory` do show up fairly
34+
often.
35+
36+
```
37+
-- Constructor name (69447 total) --
38+
45911 ( 66.109%): unnamed ==================================
39+
23536 ( 33.891%): named ==================
40+
```
41+
42+
Unnamed constructors are twice as common as named constructors.
43+
44+
```
45+
-- Non-factory body (52344 total) --
46+
44862 ( 85.706%): ; ==========================================
47+
7064 ( 13.495%): {...} =======
48+
418 ( 0.799%): {} (empty) =
49+
```
50+
51+
Of generative constructors, more than 4/5ths do not have a body. This was
52+
pretty surprising to me.
53+
54+
```
55+
-- Super initializer (24633 total) --
56+
19489 ( 79.117%): has super() ======================================
57+
5144 ( 20.883%): none ==========
58+
```
59+
60+
4/5 of constructors do have a superclass initializer in the constructor list.
61+
Not too surprising given how many classes in modern Dart code are Flutter
62+
widgets that need to forward `key` to the base class.
63+
64+
```
65+
-- Constructor parameters (69447 total) --
66+
26920 ( 38.763%): only positional parameters ====
67+
25700 ( 37.007%): only named parameters ===
68+
10631 ( 15.308%): no parameters ==
69+
3810 ( 5.486%): both positional and named parameters =
70+
1638 ( 2.359%): only optional positional parameters =
71+
748 ( 1.077%): both positional and optional positional parameters =
72+
```
73+
74+
Looking at the constructor parameter lists, we see a variety of styles. Most
75+
have either all positional parameter or all named parameters with roughly equal
76+
numbers of each. The latter are typically Flutter widgets and the former are
77+
"vanilla" Dart classes.
78+
79+
```
80+
-- Field initialization (188115 total) --
81+
103520 ( 55.030%): `this.` parameter ====
82+
37620 ( 19.998%): not initialized ==
83+
34274 ( 18.220%): at declaration ==
84+
8710 ( 4.630%): initializer list =
85+
3196 ( 1.699%): `this.` parameter, initializer list =
86+
675 ( 0.359%): `this.` parameter, at declaration =
87+
112 ( 0.060%): at declaration, initializer list =
88+
8 ( 0.004%): `this.` parameter, at declaration, initializer list =
89+
```
90+
91+
This looks at the instance fields in a class and how they are initialized. It
92+
doesn't look at assignments to fields in methods or the constructor body. It's
93+
only looking to see if a field is initialized at its declaration, using a
94+
`this.`-style parameter in the constructor list, or in a constructor initializer
95+
list. A field may be initialized in multiple ways since classes can have
96+
multiple constructors.
97+
98+
Also a little suprising to me. More than half of all fields are initialized
99+
*solely* using `this.` parameters. Initializer lists are fairly rare. I
100+
suspected that many of the field initializers in initializer lists were to
101+
initialize a private field with a named parameter without the `_`, so I looked
102+
at the initializing expressions:
103+
104+
```
105+
-- Fields in initializer lists (13760 total) --
106+
9768 ( 70.988%): other ===============================
107+
2420 ( 17.587%): _field = field ========
108+
1572 ( 11.424%): field = literal =====
109+
```
110+
111+
So, yes, it's the most common specific reason for initializing a field in the
112+
initializer list that I could recognize, but all sorts of expressions appear
113+
there.
114+
115+
```
116+
-- Field mutability (187575 total) --
117+
97385 ( 51.918%): final =========================
118+
69715 ( 37.166%): var ==================
119+
11263 ( 6.005%): late ===
120+
9212 ( 4.911%): late final ===
121+
```
122+
123+
A little more than half of instance fields are `final`. Not too surprising since
124+
it plays nice with Flutter's immutable reactive model and "Effective Dart"
125+
recommends it.
126+
127+
```
128+
-- Constructor count (70913 total) --
129+
22313 ( 31.465%): 0 ==================
130+
35526 ( 50.098%): 1 =============================
131+
9269 ( 13.071%): 2 ========
132+
986 ( 1.390%): 3 =
133+
2518 ( 3.551%): 4 ==
134+
136 ( 0.192%): 5 =
135+
44 ( 0.062%): 6 =
136+
33 ( 0.047%): 7 =
137+
15 ( 0.021%): 8 =
138+
6 ( 0.008%): 9 =
139+
25 ( 0.035%): 10 =
140+
4 ( 0.006%): 11 =
141+
6 ( 0.008%): 12 =
142+
6 ( 0.008%): 13 =
143+
4 ( 0.006%): 14 =
144+
4 ( 0.006%): 15 =
145+
5 ( 0.007%): 16 =
146+
3 ( 0.004%): 17 =
147+
2 ( 0.003%): 18 =
148+
2 ( 0.003%): 19 =
149+
1 ( 0.001%): 20 =
150+
2 ( 0.003%): 21 =
151+
1 ( 0.001%): 28 =
152+
1 ( 0.001%): 68 =
153+
1 ( 0.001%): 81 =
154+
```
155+
156+
Most classes only define a single constructor. About a third of classes have
157+
no constructor at all. They either rely on the default constructor or are
158+
abstract classes that are either used as interfaces or static namespaces.
159+
160+
(The class with 81 constructors is StreamSvgIcon from stream_chat_flutter, one
161+
for each of a large number of pre-defined icons with constructor parameters to
162+
color and scale the icon.)
163+
164+
## Opinion
165+
166+
My impression from looking at the numbers is that we could cover a fairly large
167+
number of classes if we aimed for:
168+
169+
* Classes that define a single constructor.
170+
171+
* Where all the fields can be initialized from `this.` parameters (or possibly
172+
at the field declaration).
173+
174+
* But it has to support both positional and named parameters.
175+
176+
* Supporting const constructors would be good but not necessary.
177+
178+
* Likewise we can get far with just unnamed constructors, but named is pretty
179+
useful.
180+
181+
* Some ability to call the superclass constructor is necessary, though I think
182+
[`super.`](https://github.com/dart-lang/language/issues/1855) would mostly
183+
solve this.

0 commit comments

Comments
 (0)