Side effects in programming are when a function changes the (logical) state of a computer. Affects the results obtained after that. A typical example is the assignment of values to variables.
[wikipedia](https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3% From 82% B0% E3% 83% A9% E3% 83% A0))
It is a process that does not always return the same result even if it is called in the same way (roughly speaking).
** Difficult to unit test! !! ** **
(Hereafter, in this article, automatic testing of source code using a testing framework like JUnit
is called unit testing.)
It causes a lot of trouble
I wanted to unit test this class, but it seems impossible.
Greet.java
@AllArgsConstructor
public class Greet {
String first;
String last;
String gender;
public void greet() {
System.out.println(
getFirstMessage() + forFullName() + getForGender()
);
}
private String getFirstMessage() {
int hour = LocalDateTime.now().getHour();
if (6 <= hour && hour < 12) {
return "Good morning";
} else if (12 <= hour && hour < 18) {
return "Hello";
} else {
return "Good night";
}
}
private String getForGender() {
if (gender.equals("M")) {
return "Kun";
} else {
return "Chan";
}
}
private String forFullName() {
return first + " " + last;
}
}
Main.java
public class Main {
public static void main(String[] args) {
Greet greet = new Greet("Yamada", "Takashi", "M");
greet.greet();
}
}
private
methods have field-based resultsFor these reasons, I feel this Greet
is full of side effects
Processing that does not always return the same result even if called in the same way
For example, even if you execute it in the same way as greet.greet ();
, the result will differ depending on the time of execution.
How will it be displayed? I don't know when this will be done ** when **!
Also, for example, if you do the following, the code on the 2nd and 4th lines is exactly the same, but the result is different.
Main.java
Greet greet = new Greet("Yamada", "Takashi", "M");
greet.greet();
greet.gender = 'F';
greet.greet();
For example, if you write a test below in the morning, it's clear that it will fail at night.
greet.greet() == 'Hello Takashi Yamada'
Besides, greet ()
is void
in the first place
It doesn't even give me the opportunity to compare values because I'm happy with the standard output and it doesn't return anything.
(Of course, you can't do it exactly, but you need a hack for the current time and a hack for standard output.)
Of course I will fix it This code is unacceptable (although personally)
Take a look at Greet
and organize what you are doing
Are you doing 5 things
When it comes to unit testing, the problem is how to implement 1 and 5.
You should see the campaign only on Saturdays, turn off the power at night, batch processing at the beginning of the end of the month, judge whether it is within the business hours of the partner company, etc.
As a general rule, time should be passed from outside the decision logic (depending on the layer structure and component design).
In this example, you can't test Greet
without passing time from Main
.
Then, if the assembled result is output as standard, it cannot be tested. So the result of the logic must be returned
Let's fix it
Greet.java
@AllArgsConstructor
public class Greet {
String first;
String last;
String gender;
LocalDateTime now; //Get it without generating it yourself
public String greet() {
return getFirstMessage() + forFullName() + getForGender(); //return
}
private String getFirstMessage() {
int hour = now.getHour();
if (6 <= hour && hour < 12) {
return "Good morning";
} else if (12 <= hour && hour < 18) {
return "Hello";
} else {
return "Good night";
}
}
private String getForGender() {
if (gender.equals("M")) {
return "Kun";
} else {
return "Chan";
}
}
private String forFullName() {
return first + " " + last;
}
}
If you fix the above two, you will be able to write a test that says "** If it's X o'clock now, it should come back **".
GreetTest.groovy
class GreetTest extends Specification {
def test_1() {
setup:
def greet = new Greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 12, 30, 00))
expect:
greet.greet() == 'Hello Takashi Yamada'
}
def test_2() {
setup:
def greet = new Greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 22, 30, 00))
expect:
greet.greet() == 'Good night Takashi Yamada'
}
}
By the way, fix the point that the private
method is accessing the field in the method
This isn't always the case, and maybe it's object-oriented, but I usually write the code for the fixed example.
The benefit of fixing it is to clarify the relevant value and to eliminate the possibility that the state of the field has been updated in private
.
(I will show an example later, but I will make it a private static
method and make field access impossible)
Greet.java
@AllArgsConstructor
public class Greet {
String first;
String last;
String gender;
LocalDateTime now;
public String greet() {
return getFirstMessage(now) + forFullName(first, last) + getForGender(gender);
}
private String getFirstMessage(LocalDateTime now) { //Use only the value given as an argument
int hour = now.getHour();
if (6 <= hour && hour < 12) {
return "Good morning";
} else if (12 <= hour && hour < 18) {
return "Hello";
} else {
return "Good night";
}
}
private String getForGender(String gender) { //Similarly
if (gender.equals("M")) {
return "Kun";
} else {
return "Chan";
}
}
private String forFullName(String first, String last) { //Similarly
return first + " " + last;
}
}
It's now easier to see what value each private
method depends on only
By the way, each private
method depends only on the arguments and returns the result
In other words, the side effects of the private
method part are gone!
The fixed private
method does not have any field access
So it is possible to make it a static
method
If static
is attached, this
cannot be used, so "I will set some flags while calculating"
It is guaranteed that fields like
Given the meaning of the word static
, the result does not change dynamically depending on the state, so can you somehow feel relieved?
(Static
conversion is described together with the improvement below)
Well, finally the finish
Only greet ()
depends on the field, but you no longer need to store the value in the field separately.
Greet.java
public class Greet {
//The field is gone
public static String greet(String first, String last, String gender, LocalDateTime now) { //Here are all arguments
return getFirstMessage(now) + forFullName(first, last) + getForGender(gender);
}
private static String getFirstMessage(LocalDateTime now) {
int hour = now.getHour();
if (6 <= hour && hour < 12) {
return "Good morning";
} else if (12 <= hour && hour < 18) {
return "Hello";
} else {
return "Good night";
}
}
private static String getForGender(String gender) {
if (gender.equals("M")) {
return "Kun";
} else {
return "Chan";
}
}
private static String forFullName(String first, String last) {
return first + " " + last;
}
//All methods are now static
}
However, you no longer need to do new
, so you also need to change the caller.
GreetTest.groovy
class GreetTest extends Specification {
def test_1() {
expect:
Greet.greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 12, 30, 00)) == 'こんにちは Yamada Takashi くん'
}
def test_2() {
expect:
Greet.greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 22, 30, 00)) == 'おやすみ Yamada Takashi くん'
}
}
Now this test will pass at any time, and I can't just call it with this argument to get another result!
Certainly, I feel like that ... In the first place, what I find attractive about eliminating side effects is the influence of functional languages.
However, that's not even an impression, so I thought about it for a moment.
Yeah, this is the text I just came up with
Let's look at each one
Needless to say, the top class is the Greet
class that I fixed earlier.
It doesn't have its own value, it only depends on its arguments
What is the second class I think this is the most object-oriented class, but it is a class that expresses "things" such as "personal name" and "gender".
Let's make it appear in the previous topic.
First.java
@AllArgsConstructor
public class First {
@Getter
private final String value;
}
Last.java
@AllArgsConstructor
public class Last {
@Getter
private final String value;
}
Gender.java
public enum Gender {
M, F
}
User.java
@AllArgsConstructor
public class User {
private final First first;
private final Last last;
private final Gender gender;
public String asFullNameString() {
return first.getValue() + " " + last.getValue();
}
public boolean isM() {
return gender == Gender.M;
}
}
I made a class that represents "people" called ʻUser`, and tried moving gender determination and full name concatenation there.
I feel that this is actually a pretty good division of responsibilities.
The reason is that it turned out that the parts such as Greet
" Gender is compared with M
" and "Last name and first name are connected by half-width" are not actually related to greetings.
This is because all that is required for greetings is "to process the first and last name a little according to time and gender", and "male is M
"or" half-width connection "is a process that conforms to the" definition of a person ".
(Of course, it may not be the case depending on the specifications and design)
And of course ʻUser` unit tests, this is absolute
If you move "male judgment" and "first and last name concatenation" to ʻUser and unit test ʻUser
, the test of Greet
will only be time zone judgment and Kun-chan
Greet.java
public class Greet {
public static String greet(User user, LocalDateTime now) {
return getFirstMessage(now) + user.asFullNameString() + getForGender(user);
}
private static String getFirstMessage(LocalDateTime now) {
int hour = now.getHour();
if (6 <= hour && hour < 12) {
return "Good morning";
} else if (12 <= hour && hour < 18) {
return "Hello";
} else {
return "Good night";
}
}
private static String getForGender(User user) {
if (user.isM()) {
return "Kun";
} else {
return "Chan";
}
}
}
Greet
is refreshing again
- Classes that handle them
And last but not least, this time it corresponds to Main
.
ʻUseris created and calculated using
Greet` (code is omitted)
In other words, it might look like this
The logic is basically static
and should not be state dependent
Things hold values and behave based on them, but hide the values themselves and the logic itself (Is it equivalent to encapsulation?)
There is a treatment layer that allows the two to work together, side effects are only allowed in the treatment layer (Since side effects cannot be eliminated, I will do it here. This class test will be done using a mock etc., but it will be done again soon)
Is it the decision of Greet
to say" if you're a man, if you're a woman "? ʻUser` Isn't it?
"ʻUser` is combined with half-width space, but when sending an email, it must be combined with full-width".
You might think I thought for a moment
When I thought "logic?" Or "thing?", I made "logic-only things", but recently I've calmed down.
In other words, create "ʻUser for
Greet` "and write" Kun-chan "and" half-width combination "in the greeting-specialized user class.
If it is different for email, we will set up a separate email-specific user class.
I think this is a further division of responsibilities, or a clearer boundary. If you do this, the number of classes will increase, but I feel that the number of test items will not increase so much, and above all, the dependence will decrease.
You don't have to do something like "re-evaluate greetings due to changes in email specifications"!
Be careful if you see this area! !!
When referencing, do it outside of "logic, thing", and "logic, thing" must not depend on it Do not write the assembled value on the spot, return it to the processing layer once, and write it to the processing layer again
To put it simply, side effects are mixed if the following is required in the "logic, thing" test.
Json
, database dummy data insertion, etc.void
method in" logic, thing "POST
communication, file writing, etc. are performed.If you do these things thoroughly, you will reduce the code that is difficult to evaluate, increase the number of unit tests, and improve the pace of development.
See you again