Как улучшить свой код?

293
01 ноября 2019, 14:50

Сейчас читаю Чистый код Мартина и стараюсь улучшить свой код . У меня есть абстрактный класс 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;
}
}
  1. В книге было написано, что идеальный код - когда нет комментариев, точнее они не нужны для понимания сути. Но правильно ли будет их оставить, ведь в проекте затрагивается генетика, электроника и т.п.

  2. Также switch case ( в классе LowPassFilter ), следуя Мартину, стоило бы заменить полиморфизмом. Но мне кажется, что в моем случае это не совсем уместно ( или уместно?)

  3. Хотел бы узнать какие по-вашему мнению я допускаю ошибки, читаемый ли мой код.

Answer 1

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), но я не видел ни одного, где бы в правилах кода была явная рекомендация не использовать фигурные скобки для выделения тела оператора.

Собственно почему лучше использовать, чем не использовать. В более-менее серьезном проекте код не статичен, а постоянно меняется, причем разными людьми. В таком контексте крайне важно минимизировать возможность случайной человеческой ошибки при рефакторинге. Фигурные скобки явно выделяют тело оператора - захочешь, не ошибешься.

READ ALSO
использование класса в разных проектах

использование класса в разных проектах

у меня два вопроса я создал класс G2d для своего удобства работы с графикой:

234
Что использовать для создания книги?

Что использовать для создания книги?

Нужно чтобы в левом меню DrawerNavigation отображались главы, а на экране текст с пролистыванием между экранами, как в обычных читалкахТакже нужна...

191
Как сделать бесшовный фон, в котором элементы зеркальны?

Как сделать бесшовный фон, в котором элементы зеркальны?

В стандартном бесшовном фоне элементы просто повторяются, для этого достаточно просто написать в css:

243
Изменить белый фон изображения на фон всей страницы при помощи CSS

Изменить белый фон изображения на фон всей страницы при помощи CSS

Фон всей страницы серыйСкачал небольшой логотип, естественно на странице он отображается как белый квадрат внутри которого расположен логотип

186