Сейчас читаю Чистый код Мартина и стараюсь улучшить свой код . У меня есть абстрактный класс Filter :
package filters;
import java.util.ArrayList;
import static java.lang.Math.pow;
/**
* This abstract class allows to define certain filter properties
* and writing a detailed implementation of selected methods for the
* low-pass filter and high-pass filter.
*
* @autor Andrii Shyrokov
*/
public abstract class Filter {
/**
* Private field storing magnitudes
*/
private double[] magnitudeCoordinate;
/**
* Private field storing omegas
*/
private double[] cutoffFrequencyCoordinate;
/**
* @return transfer function as a string
*/
public abstract String getTransferFunction();
/**
* This method returns the absolute value of the transfer function at specific
* frequency.
*
* @param omega is a component of the Laplace transform variable s=j*omega.
* @return absolute value of a transfer function in the frequency domain.
*/
protected abstract double getAbsoluteValue(int omega) throws Exception;
/**
* This method use 20 log |H(jw)|to compute the magnitude in dB.
*
* @param omega is a component of the Laplace transform variable s = j*omega.
* @return magnitude in dB.
*/
protected abstract double getAmplitudeRatio(int omega) throws Exception;
/**
* This method creates a random filter randomly generating an amplifier and
* time constant.
*/
public abstract Filter createRandomFilter() throws Exception;
/**
* This method mutate transfer function of the filter. Mutation is a genetic
* operator used to maintain genetic diversity from one generation of a population
* of genetic algorithm chromosomes to the next. It is analogous to biological mutation.
*/
public abstract void mutateFilterTransferFunction() throws Exception;
/**
* This method doing recombination of the transfer function. Crossover, also
* called recombination, is a genetic operator used to combine the genetic
* information of two parents to generate new offspring.
*/
public abstract void recombination(Filter filter) throws Exception;
/**
* @return an array of magnitudes.
*/
public double[] getMagnitudeCoordinate() {
return this.magnitudeCoordinate;
}
public double[] getCutoffFrequencyCoordinate() {
return this.cutoffFrequencyCoordinate;
}
/**
* This method calculates an array of magnitudes using information
* about content of the omega's array.
*/
void calculateMagnitudePlot() throws Exception {
double[] magnitudeCoordinate = new double[21];
double[] cutoffFrequencyCoordinate = new double[21];
for (int i = -10; i < 11; i++) {
try {
getAmplitudeRatio(i);
} catch (Exception e) {
e.printStackTrace();
}
magnitudeCoordinate[i + 10] = getAmplitudeRatio(i);
cutoffFrequencyCoordinate[i + 10] = pow(10, i);
}
this.magnitudeCoordinate = magnitudeCoordinate;
this.cutoffFrequencyCoordinate = cutoffFrequencyCoordinate;
}
ArrayList<Double> getCutoffFrequency() {
ArrayList<Double> cutoffFrequency = new ArrayList<>();
for (int i = -10; i < 11; i++) {
cutoffFrequency.add(1 / pow(10, i));
}
return cutoffFrequency;
}
public abstract int getFilterKey();
public abstract double getLPFTimeConstant();
public abstract double getHPFTimeConstant();
public abstract double getLPFAmplifier();
}
И наследник (их на самом деле много, но они похожи в реализации):
package filters;
import java.util.ArrayList;
import java.util.Random;
import static java.lang.Math.*;
public class LowPassFilter extends Filter {
private final int filterKey = 0;
private double amplifier;
private String transferFunction;
private double timeConstant;
private ArrayList<Double> cutoffFrequency;
public LowPassFilter(double amplifier, double timeConstant) throws Exception {
cutoffFrequency = getCutoffFrequency();
this.amplifier = amplifier;
this.timeConstant = timeConstant;
transferFunction = getTransferFunction();
calculateMagnitudePlot();
}
public LowPassFilter() {
cutoffFrequency = getCutoffFrequency();
}
@Override
public String getTransferFunction() {
transferFunction = amplifier + "/" + "(" + timeConstant + "s+" + 1 + ")";
return transferFunction;
}
@Override
protected double getAbsoluteValue(int omega) throws Exception {
if (omega < -10) throw new Exception("The number is less than 10^-10, current value: " + pow(10, omega));
if (omega > 10) throw new Exception("The number is more than 10^10, current value: " + pow(10, omega));
double numerator = amplifier;
double denominator = sqrt(1 + pow(pow(10, omega), 2) * pow(timeConstant, 2));
return numerator / denominator;
}
@Override
protected double getAmplitudeRatio(int omega) throws Exception {
return 20 * log10(getAbsoluteValue(omega));
}
@Override
public Filter createRandomFilter() throws Exception {
timeConstant = getCutoffFrequency().get(new Random().nextInt(21));
amplifier = new Random().nextInt(2) + 1;
return new LowPassFilter(amplifier, timeConstant);
}
@Override
public void mutateFilterTransferFunction() throws Exception {
cutoffFrequency = getCutoffFrequency();
int LPFMutationPosition = new Random().nextInt(2);
switch (LPFMutationPosition) {
case 0:
amplifier = new Random().nextInt(2) + 1;
break;
case 1:
timeConstant = cutoffFrequency.get(new Random().nextInt(21));
break;
default:
break;
}
transferFunction = getTransferFunction();
calculateMagnitudePlot();
}
@Override
public void recombination(Filter filter) throws Exception {
cutoffFrequency = getCutoffFrequency();
int LPFRecombinationPosition = new Random().nextInt(2);
switch (LPFRecombinationPosition) {
case 0:
if (filter.getFilterKey() == 1) {
amplifier = new Random().nextInt(1) + 1;
} else {
amplifier = filter.getLPFAmplifier();
}
break;
case 1:
if (filter.getFilterKey() == 0) {
timeConstant = filter.getLPFTimeConstant();
} else if (filter.getFilterKey() == 1) {
timeConstant = filter.getHPFTimeConstant();
} else if (filter.getFilterKey() == 2) {
timeConstant = filter.getLPFTimeConstant();
} else if (filter.getFilterKey() == 3) {
timeConstant = filter.getLPFTimeConstant();
}
break;
default:
break;
}
transferFunction = getTransferFunction();
calculateMagnitudePlot();
}
@Override
public int getFilterKey() {
return this.filterKey;
}
@Override
public double getLPFTimeConstant() {
return this.timeConstant;
}
@Override
public double getHPFTimeConstant() {
return 0;
}
@Override
public double getLPFAmplifier() {
return this.amplifier;
}
}
В книге было написано, что идеальный код - когда нет комментариев, точнее они не нужны для понимания сути. Но правильно ли будет их оставить, ведь в проекте затрагивается генетика, электроника и т.п.
Также switch case ( в классе LowPassFilter ), следуя Мартину, стоило бы заменить полиморфизмом. Но мне кажется, что в моем случае это не совсем уместно ( или уместно?)
Хотел бы узнать какие по-вашему мнению я допускаю ошибки, читаемый ли мой код.
1
Нет ничего плохого в том, чтобы описывать цели классов/методов в публичных интерфейсах и абстрактных классах, которыми будут пользоваться другие люди. Скорее даже желательно документировать.
С другой стороны, если пользователи вашего кода знают, что такое calculateMagnitudePlot и как оно должно вычисляться, то, возможно, подробное описание и не нужно.
Вот тут(и в похожих местах) комментарий излишен, так как он не повышает понимания сути. Суть абсолютно понятна из декларации поля.
/**
* Private field storing magnitudes
*/
private double[] magnitudeCoordinate;
2
По поводу case - у вас тут не полиморфизм нужен, а декомпозиция, поскольку, по сути, вы накладываете на метод несколько обязанностей.
3
Хорошим тоном считается не использовать однострочные if'ы, а в любом случае использовать блок {}
А вот тут, наоборот, вполне можно обойтись без промежуточной переменной (но это вкусовщина)
public String getTransferFunction() {
transferFunction = amplifier + "/" + "(" + timeConstant + "s+" + 1 + ")";
return transferFunction;
}
Ну и в целом у вас сильная недостача применения принципа единственной ответственности
Есть принципиальна необходимость отдавать реализацию этх полей/методов на откуп дочернему классу?
private final int filterKey = 0;
private double amplifier;
private double timeConstant;
public abstract int getFilterKey();
public abstract double getLPFTimeConstant();
public abstract double getHPFTimeConstant();
public abstract double getLPFAmplifier();
UPD Про фигурные скобки в if, for, while и т.д.
Немного цитат.
Google Java Style Guide
Braces are used with if, else, for, do and while statements, even when the body is empty or contains only a single statement.
Oracle Java code convention
if statements always use braces {}.
php-src code standarts
Always prefer::
if (foo) {
bar;
}
to:
if(foo)bar;
Clean code by Robert Martin
Sometimes the body of a while or for statement is a dummy, as shown below. I don’t like
these kinds of structures and try to avoid them. When I can’t avoid them, I make sure that
the dummy body is properly indented and surrounded by braces
Есть много проектов, где данная ситуация специально не оговаривается, либо допускается в случае очень простого и короткого тела (например linux kernel), но я не видел ни одного, где бы в правилах кода была явная рекомендация не использовать фигурные скобки для выделения тела оператора.
Собственно почему лучше использовать, чем не использовать. В более-менее серьезном проекте код не статичен, а постоянно меняется, причем разными людьми. В таком контексте крайне важно минимизировать возможность случайной человеческой ошибки при рефакторинге. Фигурные скобки явно выделяют тело оператора - захочешь, не ошибешься.
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости