Piętnuję styl 'pseudo funkcyjny', taki w którym koder leci z filter, map, reduce, pipeline bo modne, a side effects wyłażą bokami jak dziury w skarpetkach
*Uproszczona symulacja
*
JSON lista obiektów z produktem i ceną
cena większa od 40 to cena o połowę w dół, do zwrotu lista promocyjna
oczywiście bazowa lista produktów nie ma się zmieniać
Leci sobie kod (przykłady - bdnopis), biały człowiek patrzy, widzi map, zakłada na szybko, że to pure function
a to zmyłka, badPromotion - mutuje przy okazji oryginalna lista cen
const badPromotion = products =>
products
.map(product => {
if (product.price > 40) {
product.price /= 2;
}
return product;
});
const promotion = products =>
products
.map(product => {
const p = {name: product.name, price: product.price}
if (product.price > 40) {
p.price = p.price /= 2;
}
return p;
});
module.exports = {promotion, badPromotion}
Dla porządku testy, tylko promotion() nie mutuje otrzymanych danych o cenach
const {promotion, badPromotion} = require('./products');
describe('Promotion related tests', () => {
function getProducts() {
return [
{name: 'banana', price: 20},
{name: 'orange', price: 40},
{name: 'apple', price: 30},
{name: 'plum', price: 10},
{name: 'peach', price: 50}
];
}
let products;
beforeEach(() => {
products = getProducts()
})
it('badPromotion should modify original prices', () => {
const promoProducts = badPromotion(products);
expect(products[4].price).toEqual(25);
expect(promoProducts[4].price).toEqual(25);
})
it('promotion should not modify original prices', () => {
const promoProducts = promotion(products);
expect(products[4].price).toEqual(50)
expect(promoProducts[4].price).toEqual(25)
})
})
Myślę, okropny JavaScript, jakbym to była rozgadana Java to od razu by się rzuciło w oczy.
No to sprawdzam
Pojo
package pl.bv.p48;
import lombok.Data;
@Data
public class Product {
private String name;
private int price;
}
Promocje
package pl.bv.p48;
import java.util.List;
import java.util.stream.Collectors;
public abstract class Promotion {
public static List<Product> badPromotion(List<Product> products) {
return products
.stream()
.map(product -> {
if (product.getPrice() > 40) {
product.setPrice(product.getPrice() / 2);
}
return product;
})
.collect(Collectors.toList());
}
public static List<Product> promotion(List<Product> products) {
return products
.stream()
.map(product -> {
final Product p = new Product();
p.setName(product.getName());
p.setPrice(product.getPrice());
if (product.getPrice() > 40) {
p.setPrice(p.getPrice() / 2);
}
return p;
})
.collect(Collectors.toList());
}
}
I testy
package pl.bv.p48;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
public class PromotionTest {
private List<Product> products;
@Before
public void setUp() throws Exception {
Product apple = new Product();
apple.setName("apple");
apple.setPrice(20);
Product orange = new Product();
orange.setName("orange");
orange.setPrice(50);
products = Arrays.asList(apple, orange);
}
@Test
public void badPromotion() {
final List<Product> updatedProducts = Promotion.badPromotion(products);
assertThat(products, not(sameInstance(updatedProducts)));
assertThat(updatedProducts.get(1).getPrice(), equalTo(25));
assertThat(products.get(1).getPrice(), equalTo(25));
}
@Test
public void goodPromotion() {
final List<Product> updatedProducts = Promotion.promotion(products);
assertThat(products, not(sameInstance(updatedProducts)));
assertThat(updatedProducts.get(1).getPrice(), equalTo(25));
assertThat(products.get(1).getPrice(), equalTo(50));
}
}
I co? I zachowuję się jak szeryf w Teksasie. Widzę pipeline filter, map, reduce to myślę "no side effect". Szeryf widzi białego albo czarnego i też od razu szufladkuje. To wygląda, że będzie dobre a to nie.
Nic z tego, szukasz buga, szukasz złodziei, w obu przypadkach nie upraszczaj sobie pracy, nie idź na skróty, analizuj.