Conclusion iii

Contents

Programming is a lot more than just writing algorithms or programs. Programming is all about communication. Communication with others: not only with the other programmers who will be involved in your development effort, but also with yourself. Indeed, finding good names is a really important task because using the right name often opens the door to new spaces where your design can bloom and expand.

The purpose of a programming style guide such as this book is to provide a simple vehicle for addressing the needs of good communication. The goal is to make the source code clear, easy to read, and easy to understand and extend. These conventions are not cast in stone, but they set the foundation of a common culture. Culture is important when programming.

We were influenced by the excellent little book called Smalltalk with Style. We hope that you will enjoy this one and that it will help you become a better communicative designer.

Feedback and suggestions are welcome at stephane.ducasse@inria.fr.

Pull requests on https://github.com/SquareBracketAssociates/Booklet-PharoWithStyle are also welcome.

Special thank to Réné-Paul Mages, Christopher Furhman, Benoit St Jean and Masashi Fujita, Nathan Reilly, Esteban Maringolo, Hernán Morales for their feedback.

S. Ducasse -12 February 2022.

C H A P T E R 1

General naming conventions

In this chapter, we will start with guidelines about names. Indeed, names are important. And using good names is the cornerstone of writing good software. This is true in any language. We will never repeat it enough. Now, finding a good name is sometimes difficult. To help, good names are often driven by a domain or an ecosystem. Finally, the object-oriented programming paradigm gives some context that is useful: For example, the "Don't ask, tell" motto favors active and direct orders for method names.

Guideline: Favor simple direct meaning

Some native English speakers use more precise, but less common terms. Consider that your software may be read by people from different cultures. So use simple, mainstream, and common terms. Note that this does not mean that you should not use precise terms. Avoid hidden or implied meanings that can only be understood by a limited group of people. Make information explicit and clear.

Guideline: Use descriptive names

Choose descriptive names that capture domain entities unambiguously. Avoid cryptic names. Characters are cheap, so do not count them. If you are slow at typing then use the completion engine.

Guideline: Pay attention to meaning

Pharo uses English as main language. It looks strange to mention it, but this has some implications. We should pay attention to two mistakes that non native english speakers tend to do: first, adjective inversion, and second, keeping the plural form during inversion of composition (using of).

Adjective inversion

Non-English native speakers often misplace word qualifiers (adjective). In English, the qualifier is often before the word it qualifies. In Pharo, we follow such a convention.

Prefer

No plural inside

In English, the names of methods can also be written method names. Notice that in such a situation there is no s in method.

Prefer userAssociation over

Not usersAssociation

Avoid homographs

Compare the three following variables:

sizeToRead sizeJustRead readSize
In this situation, avoid homographs (https://en.wiktionary.org/wiki/homograph). That is, words that are written the same way but can have different meanings or pronunciations. For example, Did you read that book? ... Yes, I read it yesterday. About readSize: does this mean that the size was just read (red) or that it is the size to read (reed)?

Favor words with a unique pronunciation.

Guideline: Follow domain

Follow the domain concepts and culture of the project. Do not invent your own terms because you think they are better. Favor regularity (consistency) over preciseness.

Prefer

GnatXmlNode

over not GNAT_XML_Object

When representing the XML Ada abstract syntax tree in Pharo, we should not follow Ada naming conventions. The name should convey that the class is an abstract syntax tree node. Hence, GnatXmlNode is much better than GnatXm-lObject.

Another example is the following: In Moose, an importer is an object creating FAMIX entities (classes, methods, etc.) from the data structure representing a language element, usually an Abstract Syntax Tree (AST). Therefore GNATInstaller, which creates entities from an AST, should be renamed GnatImporter and GNATImporter, which loads an AST in memory should be renamed GnatASTLoader.

Guideline: Favor unique meaning and pronunciation

Choose names that have a unique meaning. Avoid homographs.

Prefer

sizeToRead sizeJustRead over Not readSize Does this mean that the size was just read (red) or is it the size to read (reed)?

Guideline: Limit or use abbreviations consistently

Abbreviations or acronisms are often obscure to newcomers. Now they are often handy so when you use them use consistently.

Prefer

ASTNode over

TreeNode AbstractSyntaxTreeNode

Guideline: Avoid numbers

It can be handy when trying something new to reuse an identifier and add a number to it. Now limit this practice to your development session. Indeed, a number does not help the reader understand the difference. As a general principle, keeping multiple implementations of the same classes is a way to propose rotten code. So if you want to keep several versions then make sure that why several versions are kept is made clear.

Prefer

StackBasedInlineParser NewInlineParser CleanerInlineParser over InlineParser2

Conclusion

The guidelines presented so far are general. We will see that we also have guidelines for classes and variables.

C H A P T E R 2

About class names

In object-oriented programming, classes play an important role. They are factories of objects, and as such, they are important for conveying the main abstractions within an application.

Guideline: Use direct and natural style

Often, classes represent objects, even abstract ones. Use the names that fit the best without inventing some clunky new terms.

Prefer

Terminal over PlaceToDisplayInformation Some classes represent an action (such as Visitor) or process.

RBReadBeforeWrittenTester MicHTMLWriter

Finally, some may represent a particular state. In Pharo, announcements follow the convention of finishing with the verb in the past tense. It contradicts English conventions, but it is applied uniformly.

IcePackageLoaded MCVersionSaved RubBoundsChanged

Avoid class name using gerund

Guideline: Avoid name collisions

To avoid name space collisions, add a prefix indicative of the project to the name of the class.

Prefer

PRDocument CmdMessage

Note You may find that Pharo is lacking a namespace. If you have a couple hundred thousand euros, we can fix that! Note, however, that even with a namespace you will have to pay attention that your namespace name does not collide with another one.

Guideline: Class names should indicate the class' parent

Suffix class names with the root class to convey the kind of object we are talking about.

C H A P T E R 3

About identifier format

While in the previous chapters we focused on identifiers or class variables, this chapter focuses on the form or format of such names. Indeed, writing software is about writing sentences that seamlessly integrate with existing ones. You do not want your program to be easily identifiable as an existing one. This is particularly true for multiple developer projects with common code ownership. Note that even if you are working on a single-person project, you will use existing class libraries and you do not want to have your 'ugly' code stepping out.

Guideline: Avoid underscores and favor camel case

The form or format of identifiers is also important. Following the form promoted by a complete ecosystem will make your software more readable and acceptable by others. This is true in any programming language.

Because Pharo and its ecosystem use camel case for class, variable, and method names. Avoid using underscores. When creating private low-level methods that bind to external C-libraries, you may want to use underscores to follow C conventions to ease tracing back the communication between libraries. In such a case, limit your use to carefully thought-out cases.

Guideline: CamelCase fully identifiers

Use consistently camel case on the full length of an identifier. Still, Pharo favors the camel case, so use it systematically for words. Wikipedia defines camel case as: Camel case (stylized as camelCase) is the practice of writing phrases such that each word or abbreviation in the middle of the phrase begins with a capital letter, with no intervening spaces or punctuation.

For local variables. Also, in this example, the method selector is not good because method names are called selectors in Pharo. In addition in English methodsNames should be written methodNames. It should be gatherSelectorsFrom: or something similar.

Conclusion

This is a bit obvious, but as a developer you should follow language conventions. This is not because Pharo is permissive that you should bend the rules. Doing so will only confuse you and other developers. So use camel case. Remember, private or local variables start with a lowercase letter, while class variables start with an uppercase letter.

C H A P T E R 4

About variable names

When choosing an appropriate name for a variable, the developer is faced with the decision: Should I choose a name that conveys semantic meaning to tell the user how to use the variable, or should I choose a name that indicates the type of object the variable is storing? There are good arguments for both styles. Let us see what the guidelines are that can help us find the right balance.

Guideline: Favor semantic variables

A semantic name is less restrictive than a type name. When modifying code, it is possible that a variable may change type. But unless one redefines the method, the semantics of it will not change. We recommend using semantically meaningful names wherever possible.

In the example below, the typed variable does not indicate how it will be used, whereas the semantic variable does.

Prefer Finding a semantic name is not always as obvious as demonstrated above.

"
There are cases in which choosing a descriptive semantic name is difficult.

Guideline: Use typed variables to indicate API

Using a type variable is an interesting way to convey the API (set of messages) that the object held in the variable responds to.

Below aDictionary conveys that the argument should have the same API as a Dictionary (at:, at:put:) Note that you can also use count instead of numberOf as in the following example:

numberOfTires tireCount

C H A P T E R 5

About method names

Method names in Pharo are called selectors. They are used in messages and are the main vehicle to convey adequate meaning to computation. From that perspective, it is really important to use them to convey the exact meaning of the computation they perform. The correct use of words and the design of selectors are then important.

Guideline: Choose selectors to form short sentences

Choose method names so that someone reading the message can read the expression as if it were a sentence.

Prefer

FileDescriptor seekTo: word from: self position over FileDescriptor lseek: word at: self position Write the test first, and make sure that your test scenario reads well.

Guideline: Use imperative verbs for actions

Use imperative verbs for message which perform an action. Pay attention to the fact that some words can be interpreted as interrogatives, whereas you want to give them an imperative meaning.

For example, compare:

optimized and triggerOptimization This is why using beOptimized would be better than a simple optimized and why isOptimized is better for the interrogative form.

Guideline: Prefix selector with as for conversion

When converting an object to another one, the convention is to prefix the class name of the target with as.

anArray asOrderedCollection

Favor the use of existing classes.

Guideline: Indicate flow with preposition

When a process state is going from one object to another, indicate the direction using meaningful names.

For example, flattenProperties: is not a good name because it does not convey where the properties will be flattened.

Guideline: Indicate returned type

When a method returns an object (different from the receiver) and this object is not polymorphic with the receiver, it is important to mention it. Since Pharo is not statically typed, we can use the selector name to give such information to the sender of the message.

For example, the method characterSeparatorMethodSignatureFor: of the pretty printer did not return a character but a block, as shown below:

characterSeparatorMethodSignatureFor: aMethodNode ^[ (self needsMethodSignatureOnMultipleLinesFor: aMethodNode) ifTrue: [ self newLine ] ifFalse: [ self space ] ]
Favor characterSeparatorMethodSignatureBlockFor: over characterSep-aratorMethodSignatureFor: when the method returns block and not a character as characterSeparatorMethodSignatureFor: indicates.

A much better design is to rewrite this method and its users to use a character. Returning a block in such a situation is overkill.

The following method is corresponding to its name. A good example is the API of the FileReference class. The message path-String indicates clearly that it returns the path as a string while to access the path object the message path should be used.

Guideline: Use interrogative form for testing

When interrogating the state of an object, use a selector beginning with a verb such as has, is, does,...

Prefer

Guideline: Use accessors or not

There are different schools about whether to use accessors. In his seminal book, Kent Beck discusses it in depth. Here we give a list of arguments for and against, and you should decide and follow the conventions of the project you work on. In any case, whether you use accessors or not, be consistent.

Arguments in favor of accessors:

• Accessors abstract from the exact state internal representation.

• Accessors may hide that values are derived or not.

• Subclasses may freely redefine the way accessors are implemented.

Arguments against accessor use:

• Accessors expose the internal state of an object.

• When the class is small, using accessors may increase the number of methods.

• Using refactorings, we can always easily introduce accessors.

Guideline: Name accessors following variable name

When you use accessors, name them consistently:

• The getter is name as the variable it refers to.

• The setter is the same but with an extra terminating colon :.

For getter, prefer

tiles ^tiles over getTiles ^tiles

Do not use get or set in accessor selectors!

Watch out

Pay attention: a Setter is just setting a value and just returning (implicitly) the receiver. The following setter definition is not correct.

BinaryNode >> root: rootNode "Set a root node" ^root := rootNode

Favor the following one instead:

BinaryNode >> root: rootNode root := rootNode Note that we do not need to comment a basic setter.

For lazy initialization:

Guideline: Avoid return in lazy blocks

You may want to use accessors to implement lazy initialization: the instance variable is initialized only if needed by checking if its value is nil.

Do not place returns inside the block.

Example

Since a style sheet acts as a dictionary of properties, it is much better to use at: instead of get:, especially if you define the message to set a value to a property as at:put: and not set:.

Guideline: Distinguish between class and instance selectors

When defining a class method, we may name it the same way as an accessor of the class. Such practice hampers code readability in the sense that it is difficult to identify rapidly class methods. The senders will report both the instance and class usage. You may think that you will identify the message because the receiver is a class or an instance, but there are many situations where this is not the case. So it's better to enrich the class method with a distinct word.

5.14 Guideline: Distinguish between class and instance selectors

Example

In Pillar, a markup syntax to write documentation, annotations have parameters and the accessor method parameters:. In some versions, an instance creation method with the same name as the accessor method exists.

Reading the code of the parser and in particular the message parameters, it is not clear whether array second is a class or an instance. To create an instance, it is better to name the method newParameter:. This way, we can immediately spot that the second element is a class. Now, if you favor a fluid interface with many parameters, using withParameters: may not be good.

Guidelines: Follow existing protocols

Protocols are ways to sort methods. It is important to place your methods into adequate protocols since it will ease future exploration of your class.

Pharo provides auto categorisation of protocols for the common methods. So use it as much as possible. If you override your specific methods, place them in similar protocols.

C H A P T E R 6

About code formatting

Code formatting improves code comprehension. Readers are used to regular formatting. Don't produce blobs of code. Copy the pros, follow the conventions.

Guideline: Be consistent

One important guideline when writing code is to follow conventions and, in addition, to be consistent. Systematically apply a formatting style. Keep the violations of conventions to a minimum.

This applies at all levels:

• Class names,

• Method names,

• Instance variable names,

• Method body, and

• Comments.

Inconsistencies will break the reading flow and understanding of the code.

Inconsistencies often indicate that different developers touched the code, and they may also be the source of bugs or indicate difficult places in the code.

Guideline: Use the general method template

• Separate method signature and comments from method body with an empty line.

• Add an extra tab to the comments.

• Add an extra line to stress the beginning of the method body.

• Use a tab to separate the method body from the left margin. Do not let space before the first word of the comment, align comment with method body, make sure that the reader can identify the beginning of the method body by placing an empty line between the method signature and the method body.

Prefer the following

Guideline: Use tabs not spaces to indent and use spaces to help reading

When navigating from one element to the next one, spaces and tabs are the same. Since they do not have a visual representation the reader cannot know in advance if the white space in front of word is a tab or multiple spaces. It is then annoying to handle spaces manually.

• Avoid extra spaces at the beginning and use tabs to indent.

• Use one space to separate instructions.

• Avoid extra spaces everywhere: one is enough!

• Avoid extra spaces at the end of the line. 1. "Obvious methods" such as accessors do not need comments.

2. A good comment does not describe how the code works in English.

3. It is better to split long methods into smaller ones with a single responsibility, 4. But a good comment is always welcome because it reinforces the understanding of the reader.

A comment should be adapted to the level of granularity (i.e., package, class, or method) to which it applies.

Guideline: Method comments

Method comments should contain sufficient information for a user to know exactly how to use the method, what the method does, including any side effects, and what it answers without having to look at the source code. Imagine that the source code is not available.

The main method comment is not about its implementation. Do not rephrase the implementation. The second level comments can include information about the implementation. Insert a new line to separate the method comments from the method body. The comments of a method should typically include:

1. the method purpose (even if implemented or supplemented by a subclass)

2. the parameters and their types 3. the possible return values and their types 4. complex or tricky implementation details 5. example usage, if applicable, as a separate comment Finally accessors do not need comments; the only comment that accessor could have is the purpose of the instance variable.

day "Answer number of days (an instance of Integer) from the receiver to January 1, 1901." ^day

Guideline: Good comment do not paraphrase code

Good Pharo source code is self-documenting, often making comments on statements redundant. Statements need only be commented upon to draw the reader's attention. If the source code implements an algorithm that requires an explanation, then the steps of the algorithm should be commented as needed.

Do not comment on an obvious fact that is expressed simply as plain code.

Guideline: Use active voice and short sentences

When writing comments, use active voice and avoid long, convoluted sentences. A method comment should state what the method does, its arguments, its effects, and its output.

Prefer "Active voice" createShell "Create the receiver's shell. Hook the focus callback."

over "Passive voice" createShell "The receiver's shell is created. The focus callback is hooked."

Guideline: Include executable comments

Pharo offers executable examples in a comment using the message >>>. Executable examples in comments are super cool because, as the reader, you can execute the code and understand the parameters. In addition, the documentation is always synchronized because tools such as the test runner can check that examples are correct.

ProtoObject >> ifNil: nilBlock ifNotNil: ifNotNilBlock "If the receiver is not nil, pass it as argument to the ifNotNilBlock block else execute the nilBlock block " "(nil ifNil:

Guideline: Use CRC-driven class comments

A class is not in isolation, but implements responsibilities (mainly one) and collaborates with other entities. Therefore a class comment should be composed of at least 3 parts: the class, its responsibilities and how it uses its collaborators. The Class Responsibility Collaboration (CRC) pattern is powerful to design but also to comment classes. Use it for commenting class.

Knowing the instance variables is the least important! Follow the template given by Pharo that is shown below.

Guideline: Use streamContents

When you want to manipulate a potentially long stream you can avoid to have to define the explicit stream creation and access using streamContents with a block whose argument is a ready to use stream.

Prefer

String streamContents: [:s | self displayStringOn: s] over stream := WriteStream on: (String new: 1000). stream ... ^stream contents

Guideline: Preallocate overly large collections

You may work on really large collections, and in such a case, the allocation of the collection can have a speed penalty.

If you know in advance how large your collection will be, preallocate a big one and store elements in it: super is used to start the method lookup in the superclass of the class of the method containing super. However, super is only necessary when the method you want to invoke has the same than the current one since using self in such a case simply creates and infinite loop.

For example, the following just creates an infinite loop, since the method initialize is calling itself infinitively.

initialize self initialize. self continue
This is the only case where you must use super to invoke a method that cannot be reached by the method lookup when it starts from the class defining the method. The correct definition is then:

initialize super initialize. self continue
Now there is no need to use super for messages that have a different selector than the method they belong to. For example, there is no need to use super bar in the following, since the defining method is called foo and you send the message bar. There is no message assert:includes:. The expression uUMLClass variables includes: 'name' should be parenthesized, because this is the result of the execution of this expression that should be passed as argument of the message assert:.

Between binary messages

Pharo does not make any assumption about the possible mathematical meaning of messages. As a programmer you cannot describe the weight of a binary messages. It means that in an expression composed of multiple binary messages, they will be executed from left to right.

For example 1 + 2 * 3 returns 9 since first the message plus is resolved and its result is the receiver of the message *.

1 + 2 * 3 >>> 9

To get the correct mathematical behavior, one should use parentheses.

1 + (2 * 3) >>> 7

Guideline: no need for extra parentheses

There is no need for parentheses surrounding unary message. There is not much benefit to add parentheses around unaray messages. In Pharo unary messages are the messages that have the highest priority. They are executed first.

No parentheses around message with higher priority

In the similar way, there is no need to put parentheses around binary messages involved in keyword-based expressions. Binary messages are executed prior to keyword-messages. In the following = is executed before ifTrue:. 10.4 Guideline: receiver of whileTrue: is a block

The receiver of the message whileTrue: is a block, and its argument is, too.

The following line is incorrect:

(number < limit) whileTrue: [ do something ]
The following line is correct: A good way to understand is that we should use [ ] when we do know whether an expression will be executed (may be multiple times).

ifTrue:ifFalse:

The conditional is always executed, while each of the arguments is a block because we do not know which ones will be executed. timesRepeat: timesRepeat:'s argument is a block because we do not know how many times it will be executed. do:/collect:

The argument of iterators such as do:, collect:,... is a block because we do not know how many times (if any) the block will be executed. The implementation of the method is however not really efficient since it copies the underlying collection during each concatenation as we show below.

The alternative is to use a write stream or to use streamContents: but using a stream makes the code more complex. Therefore there is a key question to be answered: When is it pointless to use a WriteStream and just use #, ?

Avoid #, when the code is in a loop or recursion, use a stream. Use #, when constructing error messages, class initialisation code, or situations where there is no loop.

Note that in printOn: methods, the argument is already a stream so we use it and avoid using message #,.

Difference in speed

The following snippets shows the difference in execution on large concatenations.

[

Guideline: Do not change behavior of well-known messages

When reading code, developers read it in the context of core libraries, a common vocabulary, and expected behavior. Therefore, it is important not to define methods having the same name as a common message but that exhibit different behaviors.

When you define a method, you can check really fast using implementors if existing libraries have already defined such a message.

Guideline: Use iterators

Pharo has a large collection of really powerful iterators in its deep core. Use do:, collect:, select:, reject: to start with. This is the only case where you must use super to invoke the method that cannot be reach by the method lookup when it starts from the class defining the method. The correct definition is then:

Guideline: Avoid low-level messages

Pharo offers a large set of low-level messages such as isKindOf:, isMem-berOf:, become:, canUnderstand:, doesNotUnderstand:, .... Such messages are handy for constructing tools and the infrastructure of the language. However, such methods make the code more complex and often more fragile. In addition, some are more costly and, in addition, break basic cross-references. So, always consider whether such methods are truly necessary.

10.12 Guideline: Do not abuse blocks 10.14 Guideline: Exception catch MUST not be empty

Exceptional behavior can be captured and controlled using on:do:. Now this is important not to catch an exceptional behavior and do nothing. This is basically the worst thing you can do, and it will backfire on you months later. In addition, it makes your code super difficult to debug.

11.4 Guideline: printString vs. asString

Another difficulty you may encounter is to see the difference between printString and asString.

• printString is used for debugging, that's why the default implementation shows the class name. It's implemented by specializing printOn: on your classes.

• asString converts an object into a string. It is equivalent to the toString() in other languages.

Let us take an example with the symbol #foo.

Printing the symbol #foo is just printing it as symbol.

#foo printString >>> '#foo'

Sending the message asString to the symbol #foo converts it to a string.

#foo asString >>> 'foo'

The situation is the same for strings but it can be a bit more destabilizing. The conversion of a string to a string is the string itself.

'foo' asString >>> 'foo'

Now printing a string includes the quotes and quotes should be double quoted.

'foo' printString >>> '''foo'''

C H A P T E R 12

Use Patterns

In this chapter we present some points about existing libraries or functionality that are often misunderstood and lead to code of lesser quality.

12.1 Guideline: Write small methods Long methods are a sign of less mature code. Indeed, small methods offer many strong advantages:

• Are easier to read and understand,

• Are easier to test,

• Are easier to reuse and compose,

• Are elementary pieces of customization. Indeed, sending a message to self creates a hook that subclasses can customize within their context. This is the foundation of object-oriented programming.

As measured by Zaitsev and Ducasse, Pharo methods (excluding test methods) have 3 lines of code as a median value. Therefore, this is not just anecdotal but an important point.

Guideline: Avoid singletons

Singletons are evil because they make you feel that you are writing objectoriented code, but in reality they are disguided global variables. Now you can ask yourself why this would be a problem. This is a problem when you want to modularize your code. In essence, you cannot subtitute a singleton with another object, and any change to the singleton is widely visible.

A symptom of the problem is when you want to write tests. You often do not want to have your tests impact the complete system. So think twice before using a singleton.

A good way to know if you need a singleton is whether you can add an instance variable to your domain objects and pass a reference to the object that you would like to have as a singleton. If you can avoid the singleton just with the addition of an instance variable, it means that you did not need the singleton.

This is why good object-oriented designers say that Singleton is about time and not space, meaning that you need a singleton when you cannot have two instances working at the same time -Think about a scheduler. You cannot have two scheduler manipulating threads at the same time.

The following guideline is an example of a Singleton bad use.

Guideline: Avoid explicit Transcript

We present why Transcript can be really badly used. We present that with some simple care we can develop modular solutions that are flexible and can take advantages of using Transcript without the inconvenients. Let us imagine that you still want to log strings.

First simple case

Transcript is a kind of stdout on which you can write some strings outputs. It is cheap. The class exposes a stream-based API (and this is a really important design point as we will see in the future).

Here is a typical not really good use of Some developers may think that this is not important but it can help you if one day you want to control the logging and for example use an object with the same API but to do something else. So avoid hardcoding globals. But there is more.

C H A P T E R 13

Conclusion

We hope that you enjoyed this guideline list. Remember that you write code once and will read it a thousand times. Take the time to give good names. However, finding good names is not an easy task, but you can use refactorings to improve things easily.

This goes in pairs with tests.

You write a test once and it gets executed a million times. Therefore, write tests to exercise the names you use and change them until they help you tell stories that can be understood.

  : Take advantage of automatic object initialization . . . . . . . . . . 9.2 Guideline: No automatic initialize . . . . . . . . . . . . . . . . . . . . . . . . 9.3 Guideline: No double super new initialize . . . . . . . . . . . . . . . . . . . . 9.4 Guideline: initialize does not need to return super . . . . . . . . . . . . . . . 10 Potential traps: A syntactic perspective 10.1 Guideline: Use parentheses to disambiguate messages with the same priority 10.2 Guideline: no need for extra parentheses . . . . . . . . . . . . . . . . . . . . 10.3 Guideline: receiver of ifTrue:ifFalse: is a boolean . . . . . . . . . . . . . . . . 10.4 Guideline: receiver of whileTrue: is a block . . . . . . . . . . . . . . . . . . . 10.5 Guideline: Use a Block when you do not know execution time . . . . . . . . . 10.6 Guideline: Avoid , when in loop . . . . . . . . . . . . . . . . . . . . . . . . . 10.7 Guideline: super is just self . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.8 Guideline: Do not reuse temporaries . . . . . . . . . . . . . . . . . . . . . . 10.9 Guideline: Do not change behavior of well-known messages . . . . . . . . . . 10.10 Guideline: Use iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.11 Guideline: Avoid low-level messages . . . . . . . . . . . . . . . . . . . . . . 10.12 Guideline: Do not abuse blocks . . . . . . . . . . . . . . . . . . . . . . . . . 10.13 Guideline: Do not write lengthly blocks . . . . . . . . . . . . . . . . . . . . . 10.14 Guideline: Exception catch MUST not be empty . . . . . . . . . . . . . . . . . 11 Potential traps: About printing and Streams 11.1 Guideline: printString vs. displayString . . . . . . . . . . . . . . . . . 11.2 Guideline: About printString . . . . . . . . . . . . . . . . . . . . . . . . . 11.3 Guideline: Unnecessary stream creation . . . . . . . . . . . . . . . . . . . . 11.4 Guideline: printString vs. asString . . . . . . . . . . . . . . . . . . . . 12 Use Patterns 12.1 Guideline: Write small methods . . . . . . . . . . . . . . . . . . . . . . . . . 12.2 Guideline: Avoid singletons . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.3 Guideline: Avoid explicit Transcript . . . . . . . . . . . . . . . . . . . . . . . 12.4 The real concern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.5 Guideline: Parametrized your object with log stream . . . . . . . . . . . . . . 12.6 When instance creation is delicate . . . . . . . . . . . . . . . . . . . . . . . 12.7 Guideline: Wrap explicit signal raising . . . . . . . . . . . . . . . . . . . . . .

  transform selectors do: [:each | self pushDown: each]. selectors do: [:each | class removeMethod: each]

  aConfiguration flattenProperties: aDictionaryBetter names such as flattenPropertiesFrom: and flattenPropertiesInto: are much better because there are no ambiguities. aConfiguration flattenPropertiesFrom: aDictionary aConfiguration flattenPropertiesInto: aDictionary Here are more examples changeField: anInteger to: anObject Prefer ReadWriteStream on: aCollection. over ReadWriteStream for: aCollection. Prefer File openOn: stream over File with: stream Prefer display: anObject on: aMedium over display: anObject using: aMedium

  Put accessors in the 'accessing' protocols. When you have accessors doing extra work place them in a separate protocols to stress their difference.

  tiles := OrderedCollection new. ^tiles ] or even tiles tiles ifNil: [ tiles := OrderedCollection new ]. ^tiles

  | array second parameters: (array third ifNil: [ SmallDictionary new ]) ]

  to the end of the receiver. Answer newObject." lastIndex = array size ifTrue: [ self makeRoomAtLast ]. lastIndex := lastIndex + 1. array at: lastIndex put: newObject. ^newObject

  printed as 'a, b, c' " "#('a' 'b' 'c') asCommaString >>> 'a, b, c'" ^String streamContents: [:s | self asStringOn: s delimiter: ', ']

Prefer

  : Use active voice and short sentences | result | "Store the employees who have a salary greater than in result." result := self employees collect: [:employee | employee salary > amount].

  [42] ifNotNil: [:o | o + 3 ] ) >>> 42" "(3 ifNil: [42] ifNotNil: [:o | o + 3 ]) >>> 6" ^ifNotNilBlock cull: self Object >> split: aSequenceableCollection "Split the argument using the receiver as a separator." "optimized version for single delimiters" "($/ split: '/foo/bar')>>>#('' 'foo' 'bar') asOrderedCollection" "([:c| c isSeparator] split: 'aa bb cc dd') >>> #('aa' 'bb' 'cc' 'dd') asOrderedCollection" | result | result := OrderedCollection new: (aSequenceableCollection size / 2) asInteger. self split: aSequenceableCollection do: [ :item | result add: item ]. ^result

  new: 10000. 1 to: 10000 do: [ :i | list add: i ] You can use the method bench to get a first idea. 50 8.5 Guideline: Use super to send a message with the same selector [| list | list := OrderedCollection new: 100000. 1 to: 100000 do: [ :i | list add: i ] ] bench "for 100,000 get 165.901 per second" "for 10,000 get 5484.903 per second" [| list | list := OrderedCollection new. 1 to: 100000 do: [ :i | list add: i ] ] bench "for 100,000 get 79.368 per second" "for 10,000 get 5278.544 per second" 8.5 Guideline: Use super to send a message with the same selector

  foosuper bar. self continue self assert: uUMLClass variables includes: 'name'

  named: 'ComixSerie'. uUMLClass instVar: 'name'. self assert: (uUMLClass variables includes: 'name')

  Prefer xMatrix := PMMatrix rows: x asArrayOfRows.

  over xMatrix := PMMatrix rows: ( x asArrayOfRows ). 58 10.2 Guideline: no need for extra parentheses

Prefer

  reducedX do: [ :row | (row at: 'target') = 'Iris-setosa' ifTrue: [ a add: row asArray ] ]. over reducedX do: [ :row | ( (row at: 'target') = 'Iris-setosa') ifTrue: [ a add: (row asArray) ] ]. Prefer depth: aNode "Returns depth of a tree starting from the given node" | leftDepth rightDepth | leftDepth := -1. aNode leftChild isNotNil ifTrue: [ leftDepth := self depth: aNode leftChild ]. rightDepth := -1. aNode rightChild isNotNil ifTrue: [ rightDepth := self depth: aNode rightChild ]. ^leftDepth > rightDepth ifTrue: [ 1 + leftDepth ] ifFalse: [ 1 + rightDepth ] over depth:aNode "Returns depth of a tree starting from the given node" | leftDepth rightDepth | leftDepth := -1. aNode leftChild isNotNil ifTrue: [ leftDepth := self depth: (aNode leftChild) ]. rightDepth := -1. aNode rightChild isNotNil ifTrue: [ rightDepth := self depth: (aNode rightChild) ]. ( leftDepth > rightDepth ) ifTrue: [ ^(1 + leftDepth) ] ifFalse: [^(1 + rightDepth ) ]. 10.4 Guideline: receiver of whileTrue: is a block [lastNode =0] value ifTrue:[ lastNode := curNode ] ifFalse:[ lastNode next: curNode ] [lastNode =0] ifTrue:[ lastNode := curNode ] ifFalse:[ lastNode next: curNode ]

[

  number < limit ] whileTrue: [ do something ] 10.5 Guideline: Use a Block when you do not know execution time Often newcomers get confused about when to use ( ) and [ ].

  n timesRepeat: [ lastNode := curNode next ]

  String streamContents: [:s | 1 to: 10000 do: [ :i | s << i asString ]]

Prefer

  collection do: [ :element | ... ] over 1 to: collection size do: [ :index | ... ]

Guideline: Use natural language

	RBParsing
	OpeningFile
	2.2 When defining class names, avoid abbreviations that are not obvious, avoid
	shortening names, and favor natural language.

Prefer RandomNumberGenerator over RandNumbGen Prefer CSVImporter over CSVImport Prefer RemoteControl over RemControl Prefer Player over BoardMan 2.3 Guideline: Do not expose implementation

	2.4 Guideline: Avoid to use plural for class names
	PropertyNameString
	ContactDictionary
	2.4
	A class name should not expose the implementation the class uses. This is im-
	portant because implementation may change in the future.
	Prefer
	PropertyName
	ContactBook
	over
	10

Guideline: Avoid to use plural for class names

	A class often has many instances. TestCase classes are special classes where
	their methods are individual tests. Still, it is really awkward to have a class
	ending with S.
	Prefer
	AthensTextRenderManualTest
	over
	AthensTextRenderManualTests
	Of course some domain names ends with S, in such as case just follow the con-
	vention.
	Prefer
	NetworkSystemSettings

  For example, without the Morph suffix, the reader is forced to check the superclass to understand if the class is about a graphical object or not.When you write unit tests, you are the first client of your code. You can then see if the names you chose let you write coherent and comprehensible little stories. You can explore the names you picked up and rename them.

	Prefer
	ClyBrowserButtonMorph
	over
	ClyBrowserButton
	Prefer
	ClyQueryViewMorph
	over
	ClyQueryView
	Prefer
	SpLayoutHelpTopics
	over
	SpLayouts
	In the following, not mentioning the Presenter suffix makes it unclear to the
	reader that it is a Presenter object as opposed to a Model object.
	Prefer
	ApplicationWithToolBarPresenter
	over
	ApplicationWithToolbar
	In the next example, DynamicWidgetChange does not convey that this is not
	a domain object representing a change, but a Presenter object in the Model-
	View-Presenter triad:
	DynamicWidgetChangePresenter
	over
	DynamicWidgetChange
	2.7 Conclusion
	Finding a good name is often difficult. However, nobody forces you to find it
	right upfront. And you can always change a name once you find a good one.
	A good way to find good names is to write unit tests.

Guideline: Shared variables start with uppercase

	Begin instance variables, temporary variables, method parameters, and method
	selectors with lowercase. If the word is compound, then use camel case for the
	rest.
	address
	classExtensionSelectors
	classTags
	Prefer
	| dataset f xMatrix scale x |
	Prefer
	readyForNextItem
	over
	readyFornextitem
	3.3 Begin class names, global variables, pool variables, and class variables with an
	uppercase letter. If the word is compound, then use camel case for the rest.
	Point "Class"
	Transcript "global variable"
	PackageGlobalOrganizer "class variables"

over | dataset f Xmatrix scale X |

3.5 Guideline: Convey language semantics

Remember MaxLimit, maxLimit, maxlimit, and MAXLIMIT are all different identifiers in Pharo. | MaxLimit maxLimit | MaxLimit := 10. maxLimit := 20. MaxLimit >>> 10

  NoteIn a compound word, do not confuse a prefix or suffix with a word when trying to determine which words should begin with an uppercase letter. For example, some readers may think that the "c" in subclass should be uppercase, but sub is a prefix, not a word. When in doubt about prefixes and suffixes, check a dictionary.

	OrderedCollection
	MaxLimit
	instead of
	ORDEREDCOLLECTION
	MAXLIMIT
	Prefer
	Use

Method parameters and instance variables, use maxLimit instead of maxlimit MAXLIMIT For classes, or shared variables superclass over superClass 3.6 Guideline: Method selectors start with lowercase Prefer getMethodsNamesFromAClass: aClass | methodsNames | methodsNames := aClass selectors. methodsNames do: [ :each | names add: each ] over GetMethodsNamesFromAClass: aClass | methodsNames | methodsNames := aClass selectors. methodsNames do: [ :each | names add: each ]

Guideline: Get the best from semantic and type vari- able

	"In class PhoneBook"
	phoneNumber
	name
	Prefer
	inject: anObject into: aBinaryBlock over
	number
	over labelForPerson
	inject: anObject into: aBlock
	Note that for inject:into: the best naming is to mix semantic and type nam-4.5 Guideline: Use predicate for Boolean
	ing as in
	Use predicate clauses or adjectives for Boolean objects or states. Do not use inject: initialValue into: aBinaryBlock predicate clauses for non-Boolean states.
	Prefer
	alarmEnabled isAlarmEnabled 4.3 Prefer over
	ifTrue: trueBlock ifFalse: falseBlock
	"In class Vehicle..." over numberOfTires
	ifTrue: block1 ifFalse: block2 numberOfDoors
	ifTrue: action1 ifFalse: action2
	"In class AlarmClock..." The following are other examples of good names. time
	inject: initialValue into: aBinaryBlock alarmTime
	copyFrom: start to: stop
	findFirst: aBlock ifNone: errorBlock "In class TypeSetter..."
	paddedTo: newLength with: anObject page
	font
	outputDevice
	4.4 Guideline: Use semantics for state variable
	State variable names (instance variables, class variables, or class instance vari-
	ables) are usually semantic-based. A combination of semantic and type infor-
	mation can be really powerful, too.
	Prefer

Prefer properties: aDictionary over properties: map You may also want to stress specific types in an API reference to the interface that the object implements. Prefer properties: aPuttable over properties: aCollection Suppose a String, a Symbol, and nil are valid for a parameter. A developer may be tempted to use the name aStringOrSymbolOrNil.

You may be tempted to aString or anObject. anObject is not really helping the developer who will have to use such variables. At the minimum, such a use should be accompanied by a comment that says, "anObject can be a String or aSymbol" Some developers may argue that type variables should not refer to classes that do not exist. We disagree. As shown in the following guidelines, it is a lot better to indicate that an argument is a block expecting two arguments (hence a binary block) than to just mention a block. And this works even if there is no binary block class in the system.

A good practice is to use a mixture of both semantic and typed variable names. Method parameter names are usually named after their type. Instances, classes, and temporary variables usually have a semantic name. In some cases, a combination of both can be given in the names. alarm

4.6 Guideline: Use common nouns and phrases

Use common nouns and phrases for objects that are not Boolean.

Guideline: Avoid using parameter and variable name type

  Avoid the parameter type or name in the method name if you are using typed parameter names.

	5.8 Guideline: Use accessors or not
	"useful when your class has several #at:put: methods"
	fileSystem definitionAt: aKey put: definition
	Prefer
	aFace changeTo: expression
	over
	aFace changeExpressionTo: expression
	isAtLineEnd
	over
	atLineEnd
	aVehicle hasFourWheels
	over
	aVehicle fourWheels
	5.7 Prefer
	fileSystem at: aKey put: aFile
	over
	fileSystem atKey: aKey putFiIe: aFile
	"for semantic-based parameter names"
	fileSystem atKey: index putFile: pathName

  Parentheses do not need spaces (after ( and before )) since they show that an expression fits together. But favor space for [ and ], since they may contain complex expressions.

	6.6 Guideline: Align properly
	stemSymbolFrom:aName
		|stemmer symbol|
		stemmer:=SymbolStemmer new.
		symbol:=stemmer performCrawling:aName.
		^symbol
	drawOnAthensCanvas: aCanvas bounds: aRectangle color: aColor
		(self canDrawDecoratorsOn: aCanvas) ifFalse: [ ^self ].
		self drawOnAthensCanvas: aCanvas.
		next drawOnAthensCanvas: aCanvas bounds: aRectangle color: aColor
	6.6 Guideline: Align properly
	Understanding that a piece of code is a coherent expression eases understand-
	ing of more complex expressions.
	Make sure that your indentation reinforce the identification of block of func-
	tionality.
	Prefer
	self phoneBook add:
		(Person new
		name: 'Robin';
		city: 'Ottawa';
		country: 'Canada').
	collectionNotIncluded "Return a collection for wich each element is not included in over
	'nonEmpty'" self phoneBook add:
		(Person new
	^collectionWithoutNil name: 'Robin';
		city: 'Ottawa';
	over	country: 'Canada').
	collectionNotIncluded
	" return a collection for wich each element is not included in
		'nonEmpty' "
	^collectionWithoutNil
	and over
	36

Guideline: Do not break lines randomly

  White lines attract the eyes and force the reader to ask himself why the code is separated that way. Only separate method signature and comment from method body with a new line.

		6.9 Guideline: Highlight control flow
		paragraph
		"this method is here to find the paragraph in the chain, instead of
		relying on implementing #doesNotUnderstand: !!!"
		| p |
		p := next.
		[ p isNotNil and: [ p isKindOf: RubParagraph ] ] whileFalse: [
	p := p next. ]. Prefer
	stemSymbolFrom: aName
		^p
		| stemmer symbol |
		stemmer := SymbolStemmer new.
	6.9	symbol := stemmer performCrawling: aName. ^symbol
	over
	stemSymbolFrom: aName
		|stemmer symbol|
		stemmer:=SymbolStemmer new.
		symbol:=stemmer performCrawling: aName.
		^symbol
	6.8 Prefer
	paragraph
		"this method is here to find the paragraph in the chain, instead of
		relying on implementing #doesNotUnderstand: !!!"
		| p |
		p := next.
		[ p isNotNil and: [ p isKindOf: RubParagraph ] ]
		whileFalse: [ p := p next ].
		^p
	over
	40

Guideline: Highlight control flow

  Help the reader to understand control flow logic of your code by using indentation.

	ifFalse: [^1 + rightDepth ]. depth:aNode "Returns depth of a tree starting from the given node" | leftDepth rightDepth | C H A P T E R 7
	leftDepth := -1.
	aNode leftChild isNotNil
	ifTrue: [ leftDepth := self depth: aNode leftChild ].
	rightDepth := -1.
	aNode rightChild isNotNil
	ifTrue: [ rightDepth := self depth: aNode rightChild ].
	( leftDepth > rightDepth )
	ifTrue: [ ^(1 + leftDepth) ]
	ifFalse: [^(1 + rightDepth ) ].
	Prefer
	size
		"Returns size of a tree -number of nodes in a tree"
	self root isNil ifTrue: [ ^0 ]. Prefer
	^self size: self root | block |
	block := [ :arg |
	over | local |
	size ... local ... ]
	over	"Returns size of a tree -number of nodes in a tree" self root isNil
	ifTrue: [ ^0 ]. | block local |
	^self size: self root. block := [: arg |
	... local ... ]
	And prefer
	depth: aNode
		"Returns depth of a tree starting from the given node"
		| leftDepth rightDepth |
		leftDepth := -1.
		aNode leftChild isNotNil
		ifTrue: [ leftDepth := self depth: aNode leftChild ].
		rightDepth := -1.
		aNode rightChild isNotNil
		ifTrue: [ rightDepth := self depth: aNode rightChild ].
		leftDepth > rightDepth
		ifTrue: [ ^1 + leftDepth ]

over the terribly unreadable depth:aNode method.

6.10 Guideline: Declare temporary close their use

Temporary variables that will only be used within the block should not be defined outside of the block but in the local block.

Comments

Comments are important. Comments tell readers that they are smart guys and that they correctly guessed your intentions or your code. Do not believe those who claim that methods do not require comments. Obviously, this is what they mean:

Guideline: super is just self super

  is the receiver of the message, just as self. No super is not the superclass, nor an instance of the superclass. super is the receiver of the message.There is no need to use super when returning an expression not passing it as argument. For example passing super as argument is useless and show that the developer did fully get what super it.

	10.7 Guideline: super is just self
	[ | s |
	s := ''.
	1 to: 1000 do: [ :i | s := s, i asString ] ] bench
	>>> '967.819 per second'
	10.7 foo
	anotherObject bar: super.
	self continue.
	Better use self
	foo
	anotherObject bar: self.
	self continue.
	Similarly
	foo
	^super
	Better use self
	foo
	^self
	String streamContents: [:s | 1 to: 10000 do: [ :i | s << i asString
	]] ] bench >>> '551.890 per second' 10.8 Guideline: Do not reuse temporaries
	[ | s | Readable code is key and reusing a local temporary variable to store different
	s := ''. objects during the execution of a method should be avoided. Creating a sep-
	1 to: 10000 do: [ :i | s := s, i asString ] ] bench arate temporary does not incur any execution cost. Therefore, create a tem->>> '8.465 per second' porary variable instead of storing different objects inside the same variable at
	[ String streamContents: [:s | 1 to: 1000 do: [ :i | s << i asString different execution points of a method.
	]] ] bench
	>>> '6313.137 per second'

Guideline: Do not write lengthly blocks

  Blocks are powerful. They are handy because they are like anonymous methods created on the fly. Avoid nest blocks and complex flow when explicit methods are possible.

	10.13 Guideline: Do not write lengthly blocks
	action1
	... code1
	action2
	... code2
	some method
	x <y
	ifTrue: [ self action1 ]
	ifFasel: [ self action2 ]
	over
	action1 := [ ... code1 ].
	action2 := [ ... code2 ]
	some method
	x <y
	ifTrue: [ action1 value ]
	ifFasel: [ action2 value ]
	10.13 Now, writing long blocks is a problem because blocks are not methods and you
	cannot simply call them. In addition, blocks do not create simple hook meth-
	ods that subclasses can customize.
	A good practice is to keep the block body length short and to convert long
	blocks into methods and call such methods from the block.
	Prefer
	messageWithBlock
	body of a long block
	anObject use: [ self messageWithBlock ]
	Prefer
	64

over anObject use: [ ... body of a long block ... ]

  It is not really good because it hardcodes a reference to Transcript while Pharo proposes some helpers methods such as traceCr:.

	Transcript
	myMethod
	Transcript show: 'foo' ; cr
	myMethod
	self traceCr: 'foo'

Some developers may be tempted to name setter methods by prefixing the variable name Prefer tiles: aCollection tiles := aCollection over setTiles: aCollection tiles := aCollection Following K. Beck's advice, use setTitles: only for private messages to initialize objects from class side methods.

Guideline: Use basic or raw to access low-level

When two methods are needed for the same state variable, e.g., one returning the actual stored object and one returning and raising an event, prefix the one returning the actual object with the word basic or raw.

method: aCompiledMethod self basicMethod: aCompiledMethod. self signal: MethodChanged basicMethod: aCompiledMethod method := aCompiledMethod When you have a getter that returns an object and a getter that returns a different representation of the same object, add a suffix.

path ^path pathString ^self path asString

Guideline: Follow conventions and idioms

When designing new objects, you may mimic some of the practices that the system already uses, for example, dictionaries, sets, etc.

collectionWithoutEqualElements " return a collection not including equal elements " ^collectionWithoutEqualElements

Guideline: Indent method body

Use indentation to convey structure! Do not glue everything on the left margin.

Don't indent your method like this: Separating method comments from the method implementation favor focusing our understanding to the right level. When we want to understand what the method does, we just have to read the comments. When we want to understand how the method is implemented, we just read the method body. performCrawling: aName "Takes the last word in uppercase as a symbol and eventually add it to the bag symbols" name := aName copy. self getUpperCase. self stemSymbolFrom: aName. self toUpperCase. ^symbol

Guideline: Use space to give breath to code

Gluing all the characters together slows down reading. The reader needs to separate expressions. Gluing characters also hampers the identification of logical groups such as conditional branches.

Put horizontal space to make it easier to read code and clearly identify variables, arguments, assignments, block delimiters and returns.

Guideline: Comment the unusual

When a behavior is unusual, performing unexpected actions or using an unexpected algorithm, it is important to comment on it.

In general, comments should make irregular and unusual aspects clearer. You may want to include implementation-dependent or platform specific idiosyncrasies.

C H A P T E R 8

Powerful coding idioms Some coding idioms will make your code a lot clearer. Knowing them is also good because you will code faster.

Guideline: Do not query twice for the same object

The message ifNotNil: expects a block with one argument. This argument is the object that is not nil.

For example, self doAndReturnThat ifNotNil: [ :that | self doSomethingWith: that ] is better than:

Similarly have a look at the messages containing the ifPresent: variations.

Guideline: Move return outside branches

When two branches of a condition are returning a value, better move the return out of the blocks.

When two branches of a condition are returning a value, better move the return out of the blocks.

The correct definition is just to use self. foo self bar. self continue

While using super often does not break your code, it may because if you have a method named bar in the same class this method will not be invoked. Always be cautious when reading or writing such code.

C H A P T E R 9

Object initialization

One key responsibility for a class is to correctly initialize the instance it creates. This avoids placing the burden on clients of such objects. In this chapter, we will present some patterns to initialize objects.

Guideline: Take advantage of automatic object initialization

By default Pharo offers a way to initialize your objects. The method initialize is automatically sent by the method new.

Imagine that we implement a game and that this game needs to initialize the number of turns it plays. Avoid forcing the clients of the game to have the responsibility of initializing the turn. Indeed, if the clients forget to send the expression turn:, the game logic may be simply broken.

Game new turn: 0

Therefore, when you want to initialize the state of your object, simply redefine

Game >> initialize super initialize. turn := 0.

This way Game new will automatically set the value for turn.

From a test perspective, it is often a good practice to have a test covering the default initialization.

GameTest >> testDefaultInitialization self assert: Game new turn equals: 0

Guideline: No automatic initialize

When you do not want to get the automatic initialization to happen you should use the message basicNew on the class side and create your own initialize-Something: method.

Let us imagine that the computation of the tiles of a game would be costly or that there would be no simple default. We can propose a class creation interface whose responsibility is to request the mandatory information and initialize only what is needed for the object creation.

Here we define a class method tileNumber:, and we do not invoke new but basicNew. Indeed new sends the message initialize and we do not want to pay the price for it. basicNew just allocates the new object and does not perform any other operations.

Game class >> tileNumber: aNumber ^self basicNew initializeTiles: aNumber ; yourself

Now on the instance side we can define the initialization of tiles as shown by the method initializeTiles:. Game >> initializeTiles: aNumber tiles := Array new: aNumber

If you use new instead of basicNew in previous definition, the initialize method and the method initializeTiles: will be executed.

Guideline: No double super new initialize

Since Pharo is automatically sending the message initialize as part of the object creation process using the pattern below, there is no need for the developer to do it in addition.

Now you may wonder what the problem is if you inadvertently redefine new in one of your classes as follows:

Game class >> new ^super new initialize

In Pharo, the method initialize of the class Game will be invoked twice.

Guideline: initialize does not need to return super

The method initialize is a method that modifies the receiver. It is used by convention to initialize the default value of the receiver. It is invoked by default by the message new

The following definition is not idiomatic:

First it returns a value and this value is not really used. Prefer the following definition:

Second, the ordering implies that the first local information should be computed, then one of the superclasses. It may happen that you need this order, but it is rare, so prefer the following canonical form: initialize super initialize default := 'no'.

C H A P T E R 10

Potential traps: A syntactic perspective

Understanding possible mistakes is a nice way to avoid them or to spot errors made in your code. Here are some common mistakes.

Guideline: Use parentheses to disambiguate messages with the same priority

For keyword-messages

The Pharo compiler does not know where to cut an expression composed on multiple keyword-based messages. For example, assert:includes: in the expression self assert: uUMLClass variables includes: 'name' can be a message that the object self can understand. For example testcases understand the message assert:equals: and the following expression is fully valid:

self assert: uUMLClass variables equals: 'name'.

Therefore, this is the programmer responsibility to use parenthese to separate correctly the messages having the same priority. The following example illustrates this point.

No parentheses around variable

Putting parentheses around a variable does not produce an array, it has no effect. Do not confuse parentheses and curly braces. Curly braces is a shortcut to produce an array with the elements they surround: { a } produces an array with one element whose value is the value held by the variable a as shown by the examples below.

In this code snippet, {} creates an array.

| a | a := 12. {a} printString >>> #(12)

In this snippet, the parentheses do not do anything.

No parentheses around single message

There is no need to put extra parentheses over a single message. It has no effect. Parentheses make sense to disambiguate one message over a set of messages composing an expression. Stream usage in the case of printing objects may be sometimes confusing. In this chapter we clarify some of pitfalls.

Guideline: printString vs. displayString

Newcomers are often confused between printString and displayString and their counterparts printOn: and displayStringOn::.

• printString is used for debugging, that's why the default implementation shows the class name. It's implemented by specializing printOn: on your classes.

• displayString is used for nicely display objects in list. It is implemented by specializing displayStringOn: on your classes.

If you have aPerson:

aPerson printString >>> 'aPerson ('John Doe')'

aPerson displayString >>> 'John Doe'

Guideline: About printString

Let us take a moment to step back about stream usage in printOn: methods. The printString method creates a stream and passes this stream as argument of the printOn: method as shown below: What you should see is that the method printStringLimitedTo:using: is creating a stream and passing it around.

When you redefine the method printOn: in your class, if you send the message printString on the instance variables of your object, you are in fact creating yet another stream and copying its contents in the first one.

Here is an example:

MessageTally >> displayStringOn: aStream self displayIdentifierOn: aStream. aStream nextPutAll: ' ('; nextPutAll: self tally printString; nextPutAll: ')'

This is clearly counterproductive. It is much better to send the message print: to the stream or printOn: to the instance variable it as follows:

MessageTally >> displayStringOn: aStream self displayIdentifierOn: aStream. aStream nextPutAll: ' ('; print: self tally; nextPutAll: ')'

To understand what the method print:, here its definition:

Stream >> print: anObject "Have anObject print itself on the receiver."

anObject printOn: self

Pay attention, sending the printString message is often wrong and it is the duties of the system to do it.

Guideline: Unnecessary stream creation

Unnecessary stream creation can happen in other situations than printString.

Here is an example taken from Pharo. What you should see is that a stream is created and then another stream is created and discarded with the expression protocol printString. A better implementation is the following using print:. There are two main problems:

• First what if you want to deploy your application in a system where you do not want at all to get Transcript its class and all its family. Think for example about people producing minimal images.

• Second, when Pharo is built on Jenkins all the tests are executed because we love tests. And this Transcript expression produces dirt on the build log. You do not want to have read such a trace when you are trying to understand why the build is not working. Now let us see the good way to have a log and be able to unplug it.

Guideline: Parametrized your object with log stream

The solution is really simple. Just use object-oriented programming and encapsulation. To support the Parser developer, we can simply add a stream to the class.

For example we define a new variable to and initialize it to a write stream. Then we can provide a simple setter method so that the developer can set for example the Transcript as a stream to write to.

MicAbstractBlock >> logStream: aStream logStream := aStream

When instance creation is delicate

If we do not control the creation of instances of the class using the stream, we will have difficulties configuring it with the correct stream. So if you want to be able to configure the class to use a different logger, we should define a class variable so that we can send message to the class and at initialization time, we can take the value from the class variable instead of hardcoding the WriteStream.

Here is a sketch to illustrate the idea.

We add a class variable DefaultStream to the class. We define one setter. This is using this setter that we will be able to say to the system that for a given execution it should write to the Transcript: for example doing MicAbstractBlock logStream: Transcript.

MicAbstractBlock class >> logStream: aStream DefaultStream := aStream

We make sure that the class variable is initialized to a default stream. Now we do it in way that we can later reset the stream to such default stream.

MicAbstractBlock class >> reset self logStream: (WriteStream on: (String new: 1000))

Here we hardcode the stream and alternate solution that extract this expression in a separate messages if it makes sense for subclass to specialize.

MicAbstractBlock class >> initialize self reset

And we will be able to reset the default solution either by reinitializing the class MicAbstractBlock initialize or MicAbstractBlock logStream:

MicAbstractBlock defaultValueForStream

Now we make sure that the instance creation uses the default stream hold by the class.

MicAbstractBlock >> initialize super initialize. children := OrderedCollection new. logStream := DefaultStream

The net result is that we have control and can decide what is happening. In addition, we can write tests to make sure that the logging is correct. Because using Transcript makes this a brittle exercise since someone else may write to the Transcript when you do not expect it.

Conclusion

Transcript is not bad per se. But it promotes bad coding practices. Developers should stop listening to the sirens of easy and cheap global variables. With a little bit of care and a limited infrastructure, it is possible to get the best of both worlds: modular objects and taking advantage of the existing infrastructure whose Transcript belongs to.

What we presented about Transcript can be applied to any global object or singleton. This is not because a library exposes a singleton that you have to blindly use. You should apply sound principles and protect yourself from the impact of changes.

Guideline: Wrap explicit signal raising

One question that you can ask yourself is which one of the two following situation is the best:

• Case one. Using the expression self error: 'Oh no!' and defining the method error: in your class (or calling)

• Case two. Using the expression Error signal: 'Oh no!'

The case one is better. As general principle, defining a method and sending a message is often better because sending self-send messages create hooks that your subclasses can take advantage of.

For example imagine the following situation Then you can redefine ohNoError in your subclass.

MyClassSubclass >> ohNoError Error signal: 'Oh no! Really no!'


Sooyoung Kim

MPH Christina Xiao

MPH Isabel Platt

Zafar Zafari

PhD Martine Bellanger

PhD; Peter Muennig

Health and economic consequences of applying the United States' PM2.5 automobile emission standards to other nations: a case study of France and Italy

de niveau recherche, publiés ou non, émanant des établissements d'enseignement et de recherche français ou étrangers, des laboratoires publics ou privés.

Introduction

Air pollution remains the primary environmental source of premature mortality in the European Union (EU), causing about 400,000 deaths per year 1 . Transportation is responsible for roughly half of all airborne pollutants in the EU. Regulations on transportation have led to notable improvements in air quality in the region between 1990 and 2015 1 . Nevertheless, the United States (US) is relaxing vehicle emissions standards. The impact of incremental increases or decreases in vehicle emissions standards on population health is roughly known, but less is known about the trade-offs associated with such changes on macro-economic well-being relative to health and health system costs. This paper explores the trade-off between more stringent vehicle emissions standards and regulatory costs. It does so by modeling the effect of applying more stringent standards to two case study countries. We use the relatively stringent Obama-era standard for particulate matter (PM) 2.5 as a reference because there are data on the macro-economic impacts of applying these regulations over time in the US. Likewise, a number of studies have been conducted on the relationship between vehicle emissions and air quality in France and Italy. PM2.5 refers to air particles that are 2.5 microns in diameter or less. Particles of this size can enter the circulatory system via the respiratory system, and thereby cause cardiovascular disease, lung cancer, and premature mortality [START_REF] Beelen | Effects of long-term exposure to air pollution on natural-cause mortality: An analysis of 22 European cohorts within the multicentre ESCAPE project[END_REF][START_REF] Jacquemin | Ambient air pollution and adult asthma incidence in six European cohorts (ESCAPE)[END_REF][START_REF] Schultz | Allergic disease associations with regional and localized estimates of air pollution[END_REF][START_REF] Shah | Short term exposure to air pollution and stroke: Systematic review and meta-analysis[END_REF][START_REF] Wellenius | Ambient air pollution and the risk of acute ischemic stroke[END_REF][START_REF] Zheng | Association between Air pollutants and asthma emergency room visits and hospital admissions in time series studies: A systematic review and meta-Analysis[END_REF][START_REF] Yang | An evidence-based assessment for the association between long-term exposure to outdoor air pollution and the risk of lung cancer[END_REF] . While deaths associated with PM2.5 have declined between 1990 and 2015 in the US and the EU overall, they continue to increase in Italy, Greece, and Malta 9 . The estimated number of Years-of-Life-Lost (YLL) from PM2.5 in 2014 was 852 per 100,000 inhabitants in the EU, 602 per 100,000 inhabitants in France, and 1024 per 100,000 inhabitants in Italy 1 . By contrast, YLLs in the US are small and limited mostly to the greater Los Angeles area.

Cars and trucks contribute to about half of the total PM2.5 in the EU 1 . However, there is considerable variation in the relationship between automobile emissions and PM2.5 by geographic region, with some global cities being impacted to a greater extent by factories and power plants [START_REF] Lau | Contributions of roadside vehicle emissions to general air quality in Hong Kong[END_REF] .

Current Federal regulations for vehicle emission standards set in the US by the Environmental Protection Agency 11 are stricter than the EU's current Euro Six vehicle emission standards for all major pollutants, except for carbon dioxide 1 . For PM2.5, the target annual exposure limit defined by the US EPA in 2012 is 12µg/m³ [START_REF] Giannadaki | Implementing the US air quality standard for PM2.5 worldwide can prevent millions of premature deaths per year[END_REF][START_REF]Table of Historical Particulate Matter (PM) National Ambient Air Quality Standards (NAAQS)[END_REF] .

The World Health Organization (WHO) recommends that PM2.5 not exceed 10µg/m³ 14 , a stringent limit for which there are few case study nations. We chose the US as a comparator rather than the WHO recommendation because there are extant data and at 12µg/m³, it is very close to the WHO target. The 2008 EU legislation on ambient vehicle emissions sets vehicle emissions limits for the entire EU and requires member states to place restrictions on harmful air pollutants, including pollutants from on-road vehicles [START_REF]Air quality in Europe[END_REF][START_REF]Air pollutant concentrations at station level (statistics)[END_REF][17][START_REF] Pascal | Assessing the public health impacts of urban air pollution in 25 European cities: Results of the Aphekom project[END_REF] . Under this directive, the EU member states are required to limit population exposure to PM2.5 to an annual average of 25µg/m³ by 2015 and 20µg/m³ by 2020 1, [START_REF]Air quality in Europe[END_REF][START_REF]Air pollutant concentrations at station level (statistics)[END_REF][17][START_REF] Pascal | Assessing the public health impacts of urban air pollution in 25 European cities: Results of the Aphekom project[END_REF] . In 2017, 6% to 7% of the EU urban population was exposed to PM2.5 concentrations exceeding the EU limit and about 67% were exposed to levels exceeding the WHO target [START_REF]Air quality in Europe[END_REF] .

Italy and France's PM2.5 emission exposure patterns are roughly similar to the range of emissions for other European countries. According to the EEA, the annual mean PM2.5 emission in 2015 was 13µg/m³ in France and 19µg/m³ in Italy [START_REF]Air pollutant concentrations at station level (statistics)[END_REF] , compared to an average of 13.9 µg/m³ for all EU-28 countries [START_REF]Air pollutant concentrations at station level (statistics)[END_REF] . While France and Italy fall below the current EU limit of 25µg/m³, they still exceed both the US's limit of 12 µg/m 3 and the WHO's Air Quality Guidelines of 10µg/m³ [START_REF]Table of Historical Particulate Matter (PM) National Ambient Air Quality Standards (NAAQS)[END_REF][START_REF]C89?sequence=1[END_REF]17 . Some eastern EU nations like Poland, Serbia, and Bulgaria have emissions averages that are much higher than Italy [START_REF]Air pollutant concentrations at station level (statistics)[END_REF] .

In our analysis, we estimated health impacts in terms of premature deaths and years of life lost due to PM2.5 emission exposure 1,[START_REF] Pascal | Assessing the public health impacts of urban air pollution in 25 European cities: Results of the Aphekom project[END_REF] . We also estimated economic costs associated with morbidity related to road traffic pollution in the EU [START_REF] Pascal | Assessing the public health impacts of urban air pollution in 25 European cities: Results of the Aphekom project[END_REF][START_REF] Chanel | The hidden economic burden of air pollution-related morbidity: evidence from the Aphekom project[END_REF] . Using these inputs, we developed a costeffectiveness analysis to model the effect of applying the stricter US vehicle emissions standards and enforcement to France and Italy.

Methods

Using a Markov model, we simulated relevant health and economic consequences of reduced PM2.5 emissions associated with two scenarios in France and Italy: (1) "Keep the standard as is;" and (2) "Adopt and enforce US emissions standards." We estimated qualityadjusted life years (QALYs), health system costs, regulatory costs, vehicle upgrade costs, and fuel savings [START_REF]Table of Historical Particulate Matter (PM) National Ambient Air Quality Standards (NAAQS)[END_REF] . All parameters were derived from the existing literature and are summarized in Table 1.

We ran our model over the course of the lifetime of our standard cohort and discounted future QALYs and costs at a discount rate of 3% [START_REF] Sanders | Recommendations for conduct, methodological practices, and reporting of costeffectiveness analyses: Second panel on cost-effectiveness in health and medicine[END_REF] . Final estimates of incremental costs per QALY were made in constant 2018 €. We conducted multiple one-way sensitivity analyses to quantify the robustness of our estimates against broad changes in the core parameters and assumptions of the model. Additionally, we performed a Monte Carlo simulation with 10,000 random samples to capture uncertainty in our model outcomes across all variables. We built our model in TreeAge Pro 2016 software [START_REF]TreeAge Pro[END_REF] .

PM2.5 Emissions Regulations: US vs. France and Italy

In our model, we explored the potential impact of stricter regulations in two European countries on health and costs. We limited the regulations to only light-duty vehicles, which are defined as passenger cars for everyday use 22,[START_REF]US: Vehicle Definitions[END_REF] and account for more than 80% of registered vehicles in France and Italy [START_REF]Vehicles in use -Europe 2017[END_REF] . We omitted light-and heavy-duty trucks from our analysis, including commercial trucks, because there is limited data on upgrading costs and fuel savings.

Italy and France have a similar proportion of heavy vehicles on the road, extensive rail networks, and while Italy imports a good deal of energy from France, 80% of this power comes from nuclear power generation and hydropower [START_REF] Voorspools | Modelling of electricity generation of large interconnected power systems: How can a CO2 tax influence the European generation mix[END_REF][START_REF] Beaupuy | France's Power Emissions Fell in 2019 as Coal's Share Dipped[END_REF] . This allows for a natural control between the two case study nations.

Demographic Data

We measured the impact of stricter vehicle emissions standards on all residents of France and all residents of Italy as separate arms of the model. In our Markov model, the average age, population size, and age-specific mortality rates for each country were retrieved from Eurostat statistics [START_REF]Eurostat Database[END_REF] . We also applied different regulatory standards in France, Italy, and the US to a uniform, hypothetical cohort. This allows us to provide estimates of the impacts of regulatory standards on health that are independent of socio-demographic differences between the three nations.

Cost-Effectiveness Model

Our Markov decision-analytic model had two arms: "Keep the standard as is" and "Adopt US emissions standards." Our hypothetical regulatory changes could reduce the risk of lung cancer, stroke, asthma, and overall mortality [START_REF] Pascal | Assessing the public health impacts of urban air pollution in 25 European cities: Results of the Aphekom project[END_REF][START_REF] Chanel | The hidden economic burden of air pollution-related morbidity: evidence from the Aphekom project[END_REF] . However, to ensure that our numbers are conservative, we focused on two major health effects of ambient PM2.5: asthma and cardiovascular disease (CVD). Our model, therefore, assumes five health states: perfect health, chronic asthma, chronic CVD, comorbid asthma and CVD, and death. Excluding other pollutants also helps ensure that numbers are conservative on the benefits side of the cost-effectiveness analysis.

Our Markov model simulates the impact of reduced PM2.5 levels on the risk of developing new asthma, chronic CVD, and comorbid asthma, and CVD. We also modeled the risk of acute exacerbations for people living with chronic asthma and the risk of acute CVD events such as stroke or myocardial infarction among people living with chronic CVD. We ran the model from 2018 until 2050 to evaluate the impact of the policy over the lifetime of each standardized cohort.

We then conducted a one-way sensitivity analysis using the confidence intervals reported in the literature to see how changing a given parameter would impact the incremental costeffectiveness ratio (ICER) [START_REF] Muening | Cost-effectiveness analyses in health: a practical approach[END_REF] . Finally, we developed a Monte Carlo simulation for probabilistic sensitivity analysis using a normal probability distribution based on the reported standard errors from the literature [START_REF] Muening | Cost-effectiveness analyses in health: a practical approach[END_REF] . We ran the simulation with a willingness-to-pay (WTP) threshold of €46,000, referring to the European survey on willingness-to-pay for improved air quality in an economic study [START_REF] Desaigues | Economic valuation of air pollution mortality: A 9-country contingent valuation survey of value of a life year (VOLY)[END_REF] to assess the robustness of our analysis.

Probabilities and Rates

Model parameters for the incidence rates for asthma, CVD, exacerbations, and relapses, was derived from the literature. The life tables and demographics for France and Italy were each obtained from their national statistics bureaus [START_REF]Mortality rates by sex and age [Internet[END_REF][START_REF]Lo stato di salute della popolazione -RSSP 2012-2013[END_REF] . Where a country-specific value for a given parameter was not available, we used the reported value for a demographically similar country.

Where co-morbid states are present, we adjusted health states to reflect independent probabilities [START_REF] Benjamin | Heart Disease and Stroke Statistics'2017 Update: A Report from the American Heart Association[END_REF][START_REF] Demoly | Update on asthma control in five European countries: Results of a 2008 survey[END_REF][START_REF]Population changes: Demographic balance sheet 2017 -Retrospective Tables[END_REF][START_REF]Demographic Indicators -Estimates for the year 2015[END_REF] . We averaged the reported values from as many meta-analyses and nationwide studies as possible to improve the accuracy and generalizability of our findings.

The "Adopt US standards" scenario evaluates the economic costs and health benefits of reducing PM2.5 in France and Italy to those currently seen in the US. We applied the drops in PM2.5 levels to the relevant relative risks based on data from the literature [START_REF] Jacquemin | Ambient air pollution and adult asthma incidence in six European cohorts (ESCAPE)[END_REF][START_REF] Wellenius | Ambient air pollution and the risk of acute ischemic stroke[END_REF][START_REF] Zheng | Association between Air pollutants and asthma emergency room visits and hospital admissions in time series studies: A systematic review and meta-Analysis[END_REF][START_REF] Hoek | Long-term air pollution exposure and cardio-respiratory mortality: A review[END_REF] . We then modified the baseline risk of health states in the control arm, "Keep the standard as is," to inform the corresponding probabilities in the intervention arm. For a complete list of parameters, see Table 1.

Costs

We included health care-related costs, the costs of implementing new test facilities, and fuel savings from stricter PM2.5 emissions standards as direct costs [START_REF]Final Rule for Model Year 2017 and Later Light-Duty Vehicle Greenhouse Gas Emissions and Corporate Average Fuel Economy Standards[END_REF] . We quantified indirect costs through health-related productivity gains associated with reduced PM2.5 levels [START_REF] Jacquemin | Ambient air pollution and adult asthma incidence in six European cohorts (ESCAPE)[END_REF][START_REF] Wellenius | Ambient air pollution and the risk of acute ischemic stroke[END_REF][START_REF] Zheng | Association between Air pollutants and asthma emergency room visits and hospital admissions in time series studies: A systematic review and meta-Analysis[END_REF][START_REF]Demographic Indicators -Estimates for the year 2015[END_REF] . All the costs associated with this policy change were taken from the literature.

To determine the direct costs of introducing US standards to both France and Italy, we used data from the US EPA's 2014 Regulatory Impact Analysis [START_REF]Final Rule for Model Year 2017 and Later Light-Duty Vehicle Greenhouse Gas Emissions and Corporate Average Fuel Economy Standards[END_REF] . Costs included a one-time implementation cost for PM2.5 regulation test facilities, the unit cost of upgrading vehicles with additional hardware, the indirect cost of additional labor, and the annual fuel savings after the upgrade for gasoline vehicles [START_REF]Final Rule for Model Year 2017 and Later Light-Duty Vehicle Greenhouse Gas Emissions and Corporate Average Fuel Economy Standards[END_REF] . To compute the total cost of vehicle upgrades, we multiplied the unit costs per vehicle by the annual number of light-duty vehicles sold in each country. For each health state, we included on-going costs of chronic asthma or CVD management [START_REF] Demoly | Update on asthma control in five European countries: Results of a 2008 survey[END_REF][START_REF] Chevreul | Cost of stroke in France[END_REF][START_REF] Dal Negro | Cost of persistent asthma in Italy[END_REF][START_REF] Doz | The association between asthma control, health care costs, and quality of life in France and Spain[END_REF][START_REF] Fattore | The social and economic burden of stroke survivors in Italy: A prospective, incidence-based, multi-centre cost of illness study[END_REF] , costs of asthma exacerbation [START_REF] Van Ganse | Asthma-related resource use and cost by GINA classification of severity in three European countries[END_REF] , and costs of acute CVD events [START_REF] Chevreul | Cost of stroke in France[END_REF][START_REF] Fattore | The social and economic burden of stroke survivors in Italy: A prospective, incidence-based, multi-centre cost of illness study[END_REF] .

Utilities

Health outcomes were measured in terms of QALYs, a measure of remaining life expectancy, adjusted to reflect the average state of health of a cohort [START_REF]The NICE Glossary[END_REF] . The health state utility value for chronic asthma was derived from literature [START_REF] Zafari | Cost-effectiveness of bronchial thermoplasty, omalizumab, and standard therapy for moderate-to-severe allergic asthma[END_REF][START_REF] Zafari | Cost-effectiveness of tiotropium versus omalizumab for uncontrolled allergic asthma in US[END_REF] . We multiplied the distribution of asthma health states, defined by the Global Initiative for Asthma [START_REF]Global Strategy for Asthma Management and Prevention[END_REF] , by their corresponding utility values [START_REF] Zafari | Economic and health effect of full adherence to controller therapy in adults with uncontrolled asthma: A simulation study[END_REF] . For CVD, we compiled the health utility values from the literature based on the EuroQol-5 Dimension (EQ-5D) scale [START_REF] Jiao | The cost-effectiveness of lowering permissible noise levels around U.S. airports[END_REF][START_REF] Ara | Populating an economic model with health state utility values: Moving toward better practice[END_REF] . All relevant asthma and CVD events were assigned a disutility value based on literature, found in Table 1.

To measure the impact of an ambient PM2.5 decrease on quality of life, we used the reported QALY gain/loss from the literature, and assumed a linear association between PM2.5 and QALYs.

Table 1. List of parameters used in the Markov Model

[Insert Table 1 here.]

Results

In France, the added direct and indirect costs of not adopting and enforcing the US regulations (keeping the status quo) amounted to €49,000 (95% CI €25,000, €90,000) while adopting the US PM2.5 emission standards would cost €48,000 (95% CI €24,000, €88,000). The number of QALYs associated with the status quo scenario was 19.63 (95% CI 18.47, 20.21), while the number of QALYs associated with adopting US regulations was 19.67 (95% CI 18.50, 20.24). In Italy, the cost associated with not adopting stricter PM2.5 regulations was €39,000 (95% CI €6,000, €192,000), while adopting the standards was associated with a cost of €36,000 (95% CI €5,000, €175,000). The corresponding QALYs for the status quo and new emission regulations were 27.38 (95% CI 26.15, 28.15) and 27.69 (95% CI 26.39, 28.45) respectively.

With incremental costs of -€1,000 for France and -€3,000 for Italy, as well as incremental QALYs of 0.04 for France and 0.31 for Italy, adopting US emission standards saves costs and lives for both French and Italian populations.

Table 2. Incremental costs, incremental quality-adjusted life years, and incremental costeffectiveness ratios for France and Italy for current vehicle emissions standards versus standards set in the US.

[Insert Table 2 here]

The one-way sensitivity analysis indicated that the results of our model were robust to changes to the parameter values, such as changes in the relative risk of CVD onset, ongoing cost of chronic asthma, new CVD onset incidence rate, etc. (the full list of model parameters subjected to the sensitivity analysis can be found in Appendix 1, Figure S1). The parameter that affected our model the most was variability in the relative risk of asthma incidence due to an increase in PM2.5 for France and ongoing cost of chronic CVD for Italy (Appendix 1, Figure S1).

[Insert Figure 1 Here.] From the probabilistic Monte Carlo simulation (Figure 1), 93.8% of randomly-generated samples for France and 87.4% of samples from Italy were both cost-and life-saving for adopting US PM2.5 emission standards compared to no change in air pollution policies. An additional 0.7% of French samples and 10.1% of Italian samples fell within a WTP of €46,000. Despite excluding important benefits associated with regulatory changes, less than six percent of the simulations for both France and Italy fell outside of the WTP threshold.

From the acceptability curve, with a WTP of €46,000, the intervention has 98.7% acceptability for France and 96.0% acceptability for Italy. Within the confidence interval of €27,000 -€110,000, the intervention maintained higher than 95% acceptability for both countries.

(Appendix 1, Figure S2)

Discussion

We set out to illustrate the changes in societal costs and health associated with changes in PM2.5 regulations. We used two nations as case studies in order to illustrate the tradeoffs associated with incremental regulatory changes. Cost-saving preventive health interventions are very rare, and should be implemented so long as there are no overriding ethical concerns associated with doing so [START_REF] Sanders | Recommendations for conduct, methodological practices, and reporting of costeffectiveness analyses: Second panel on cost-effectiveness in health and medicine[END_REF][START_REF] Weinstein | Recommendations of the panel on cost-effectiveness in health and medicine[END_REF] . We find that improving vehicle emissions standards and enforcement is one of those rare policies that could save both money and lives. The EU has not kept pace with the US with respect to vehicle emissions standards set by the US EPA.

Enforcement of violations is also weak in many EU member states. As a result, the EEA reports that about 400,000 deaths occur each year as a result of long-term exposure to excessive PM2.5 [START_REF]Air quality in Europe[END_REF] .

This human toll also comes with an economic toll for the EU that hits health systems particularly hard. This is striking for a block of nations that also offers near universal care to its occupants. Full quantification of the economic and health toll association with regional changes in regulation would be a massive undertaking given the large national variations in emissions, pricing of health goods, and other model inputs, and mean values for EU are of little use. Given our finding that both nations would realize savings, it is likely that most EU nations would realize similar gains. However, countries with tough regulations might experience increases in costs without meaningful gains in health. Likewise, our predictions are not valid for countries with weak regulations that might enact more radical changes that could produce unforeseen and unintended macroeconomic consequences.

Our study also serves as a warning for US policy. Currently, the US is considering relaxing environmental protections, and one EPA scientific advisor has indicated that the air in the US is "too clean" to breathe for optimal health [START_REF] Brueck | One of the EPA's newest science experts thinks 'modern air' is too clean to breathe[END_REF] . Relaxing standards, even to a small degree, would likely lead to increases in deaths, disability, and costs. This is likely to be a bigger problem in the US than in Europe not only because driving is more prevalent, but also because healthcare costs are roughly twice those of France or Italy and growing much more rapidly over time [START_REF] Charlesworth | Why is health care inflation greater than general inflation?[END_REF][START_REF] Dunn | Adjusting Health Expenditures for Inflation: A Review of Measures for Health Services Research in the United States[END_REF][START_REF] Roman | On Marginal Health Care -Probability Inflation and the Tragedy of the Commons[END_REF] . A recent study found that PM2.5 concentrations are highly predictive of Covid-19 deaths in the United States [START_REF] Wu | Exposure to air pollution and COVID-19 mortality in the United States[END_REF][START_REF] Muennig | Opinion | As Fears of Wuhan's Coronavirus Spread[END_REF] .

Our study has a number of limitations. First, we showed two case studies rather than providing mean impacts in the EU. Some nations in the EU have much higher standards than France or Italy, while others have much lower standards. Like the EU, US states vary with respect to enforcement of EPA vehicle emissions standards. However, because the US automobile market is somewhat monolithic, automobile emissions and fuel efficiency standards in the US are driven more by the state with the toughest regulations than by EPA standards.

Another limitation is that our key model inputs-pollution-associated morbidity and mortality-are not derived from randomized trials in humans (for ethical reasons). Rather, they are derived from observational and quasi-experimental studies of humans backed by experimental animal models. The health effects of pollution could be better or worse than those we present here. We account for error in estimates of these and other model inputs by using a broad sensitivity analysis and excluding potentially important pollutants from our estimates.

Conclusions

Most medical interventions cost well over $100,000 per QALY gained [START_REF] Muening | Cost-effectiveness analyses in health: a practical approach[END_REF] , however broader social policies such as education interventions, can save both money and lives [START_REF] Muennig | The cost-effectiveness of New York City's Safe Routes to School Program[END_REF][START_REF] Muennig | Considering whether Medicaid is worth the cost: Revisiting the Oregon Health Study[END_REF][START_REF] Muennig | Cost Effectiveness of the Earned Income Tax Credit as a Health Policy Investment[END_REF][START_REF] Wu | The Cost-effectiveness Analysis of Nurse-Family Partnership in the United States[END_REF] . We show that titrating regulatory controls to optimize health could be added to the armament of policies, including vaccines and education interventions [START_REF] Muennig | The cost-effectiveness of New York City's Safe Routes to School Program[END_REF][START_REF] Muennig | Considering whether Medicaid is worth the cost: Revisiting the Oregon Health Study[END_REF][START_REF] Muennig | Cost Effectiveness of the Earned Income Tax Credit as a Health Policy Investment[END_REF][START_REF] Wu | The Cost-effectiveness Analysis of Nurse-Family Partnership in the United States[END_REF] , that improve health.

Figure 1 .

 1 Figure 1. Incremental Cost-Effectiveness Scatter Plot with 95% Credibility Interval (defined as

Table 1 . List of parameters used in the Markov Model

 1 Probabilistic distribution of parameters. β denotes beta-distribution and γ stands for gamma distribution. b: Different cost values used per calendar year. Values in the table is the initial cost at year 2019.

	1
	Description	France		Italy
						Probabili
			Standa		Standa
						stic
		Base Value	rd	Base Value	rd
						Distribut
			Error		Error
						ion a
	General parameters
	Average age of target population 27	41.4	-	45.9	-	-
	Total number of target population 27	66989083	-	60589445	-	-
	Chronic asthma prevalence 33
		0.06	-	0.04	-	-
	(2010)
	Chronic CVD prevalence 32,34,35	0.054	-	0.045	-	-
	Annual discounting rate 20	0.03	-	0.03	-	-
	Ambient PM2.5 base level (μg/m 3 )	13.00	-	19.00	-	-
	Ambient PM2.5 benchmark level
		7.65		7.65
	(μg/m 3 ) 60
	Number of gasoline passenger cars
		777645	-	599752	-	-
	sold in year 2017 61,62
	Number of diesel passenger cars
		1089403	-	1061004	-	-
	sold in year 2017 61,62
	Total passenger vehicles on road
		32326000	-	37080753	-	-
	61,62
	Costs (2018 Euro)

Table 2 .

 2 Incremental costs, incremental quality-adjusted life years, and incremental cost-effectiveness ratios for France and Italy for current vehicle emission standards versus standards set in the United States. (Numbers are rounded to reflect the high degree of uncertainty in the estimates.)

	France

Acknowledgments:

Disclaimers: The research presented in this paper is that of the authors. All data is available in the main text or the supplementary materials. The TreeAge model can be provided upon request. All data collection and analysis were done in 2018, and the study is exempted from IRB approval.

Declarations of interest: none

Funding: This work is supported by Global Research Analytics for Population Health (GRAPH).