Случай (3) более проблематичен – если указатель null играет существенную роль в приложении, его добавление к контейнеру может быть оправдано. Но если для хранения пустых значений нет веских оснований, то, по всей вероятности, необходимо возбудить исключительную ситуацию.
А что же с первым фрагментом? Если он используется в небольшом проекте, то все нормально, за исключением того, что вы без особой надобности заставляете все элементы программы, которые используют класс Person1, также включать файл заголовка для класса Date. Как только подобное употребление становится обычной практикой в неком проекте, вскоре обнаружите, что включение одного файла заголовка заканчивается включением большей части системы, что существенно увеличивает время компиляции.
void showBalance(BankAccount b) {
b.printBalance();
}
markWorkflow(acct.name (), SET_BALANCE);
Программе в processTransaction не придется узнавать, какой дочерний объект в пределах BankAccount носит это имя – эта информация о структуре не должна разглашаться через контракт BankAccount. Вместо этого мы запрашиваем у BankAccount имя на счете. Он знает, где хранится имя (может быть, в объекте Person, в объекте Business, или в полиморфном объекте Customer).
1. Назначения коммуникационного порта. Ясно, что эта информация должна сохраняться в виде метаданных. Но на каком уровне детализации? Некоторые коммуникационные программы системы Windows позволяют выбирать только скорость в бодах и порт (скажем, СОМ1 – COM4). Но вероятно вам придется указать размер слова, четность, стоповые биты, и настройку дуплексной связи. Старайтесь допускать самый мелкий уровень детализации, там где это разумно с практической точки зрения.
2. Поддержка выделения синтаксических конструкций различных языков в программе редактирования. Она должна быть реализована в виде метаданных. Вы же не хотите, чтобы вам пришлось переделывать программу только потому, что в последней версии языка Java было введено новое ключевое слово.
3. Поддержка редактора для различных графических устройств. Эту поддержку было бы трудно реализовать исключительно в виде метаданных. Вам не хотелось бы нагружать приложение многими драйверами устройств только для того, чтобы выбрать один из них во время выполнения программы. Однако вы могли воспользоваться метаданными для указания имени драйвера и динамической загрузки программы. Это еще один аргумент для сохранении метаданных в удобочитаемом формате; если вы используете программу для установки дисфункционального видеодрайвера, то вы не сможете переустановить его, пользуясь этой программой.
4. Конечный автомат для программы синтаксического анализа или сканера.
Это зависит от того, анализируете вы или просматриваете. Если анализируете некоторые данные, которые жестко определены в стандартах и скорее всего не будут изменены без одобрения конгресса США, то жесткое кодирование вполне годится. Но если вы сталкиваетесь с более изменчивой ситуацией, то может быть, более выгодным является внешнее определение таблиц состояний.
5. Типовые значения и результаты, используемые в модульном тестировании.
Большинство приложений определяет эти значения как встроенные в тестовый стенд, но вы можете добиться большей гибкости, перемещая тестовые данные и определение приемлемых результатов за пределы самой программы.
public interface Passenger {
public void waitListAvailable();
}
public interface Flight {
...
public void addWaitListListener(Passenger p);
public void removeWaitUstUstener(Passenger p);
public void addFullListener(FullListener b);
public void removeFullListener(FullListener b);
...
}
public interface BigReport extends FullListener {
public void FlightFullAlert(Flight f);
}
При неудачной попытке добавить Passenger, поскольку рейс полностью забронирован, мы можем (как вариант) поместить Passenger в лист ожидания. При открытии вакансии производится вызов метода waitListAvailable. Затем этот метод может осуществить выбор: либо добавить Passenger автоматически, либо дать указание сотруднику авиакомпании позвонить заказчику и выяснить, заинтересован ли он еще в рейсе, и т. п. Теперь мы обладаем достаточной гибкостью, чтобы избрать линию поведения, исходя из пожеланий клиента.
Кроме того, мы хотим избежать ситуаций, при которых BigReport разбирает тонны записей, отыскивая полностью забронированные рейсы. Зарегистрировав BigReport в качестве «слушателя» Flights, каждый индивидуальный Flight может сообщать, когда он полностью (или почти полностью) забронирован. Теперь пользователи могут мгновенно получить оперативные, с точностью до минуты, сообщения из BigReport, а не ожидать часами окончания его работы, как это было раньше.
1. Обработка изображения.Для простого распределения рабочей нагрузки между параллельными процессами более чем адекватной может оказаться общедоступная очередь работ. Вы можете рассмотреть систему "доска объявлений" при наличии обратной связи, т. е. если результаты обработки одного фрагмента изображения влияют на другие фрагменты так, как это происходит в системах искусственного зрения или сложных трехмерных преобразованиях изображений.
2. Календарное планирование для групп.Для этого "доска объявлений" очень даже пригодится. Вы можете поместить назначенные собрания и готовность на "доску объявлений". Есть объекты, функционирующие автономно; в данном случае очень важна обратная связь, а участники могут приходить и уходить.
Можно рассмотреть возможность разделения "доски объявлений" в зависимости от того, кто осуществляет поиск: младший персонал может заботиться только о локальном офисе, отдел кадров интересоваться только англо-говорящими офисами во всем мире, а исполнительный директор – всем сразу.
В форматах данных имеется некий элемент гибкости: мы можем проигнорировать форматы или языки, которых не понимаем. Нам придется понимать различные форматы только для тех офисов, которые встречаются друг с другом, и не придется подвергать всех участников полному транзитивному замыканию всевозможных форматов. При этом связанность уменьшается там, где нужно, и мы не имеем искусственных ограничений.
3. Средство мониторинга компьютерной сети.Это весьма сходно с программой обработки заявлений на ипотечный кредит/ссуду, описанной в примере приложения (с. 153). На доску помещаются сообщения о неисправностях, присылаемые пользователями, и автоматические генерируемые статистические данные. Сотрудник (или программный агент) может анализировать "доску объявлений", чтобы осуществлять диагностику неисправностей в сети: две ошибки в линии могут быть отнесены на счет космических лучей, но 20000 ошибок говорят о проблеме в аппаратном обеспечении. Подобно детективам, разгадывающим тайну убийства, вы можете использовать множественные объекты, анализируя и внося свою лепту в решение проблем, связанных с компьютерной сетью.
Во-вторых, есть проблематичный оператор gets, который будет записывать столько символов, сколько он получит в переданный буфер. Злонамеренные пользователи использовали это, когда им не удавалось проделать бреши типа buffer overrun в защите многих различных систем. Никогда не пользуйтесь gets().
В-третьих, программа предполагает, что пользователь понимает английский язык.
И наконец, никто, находясь в здравом уме, не станет прятать средство взаимодействия с пользователем в недра библиотечной подпрограммы.
Если ваши результаты не ложатся на гладкую кривую, то вы захотите проверить, не используется ли мощность вашего процессора каким-либо другим процессом. По всей вероятности, вы не получите хороших показателей в многопользовательской системе, и даже если окажетесь единственным пользователем, можете заметить, что фоновые процессы периодически отбирают циклы у ваших программ. Вы также можете захотеть проверить использование памяти: если приложение начинает использовать область свопинга, то производительность резко снижается.
Интересно поэкспериментировать с различными компиляторами и установочными параметрами оптимизации. Мы обнаружили, что весьма впечатляющее ускорение стало возможно, благодаря использованию агрессивной оптимизации. Мы также обнаружили, что на более распространенных архитектурах типа RISC компиляторы фирмы изготовителя часто превосходили по быстродействию более переносимую GCC. Возможно, изготовитель посвящен в тайны эффективной генерации программ на этих машинах.
Сбалансированное двоичное дерево содержит вдвое больше элементов на каждом уровне. Дерево глубиной D содержит 1+2+4+8 +… +2^(D-1), или 2^D – 1 элементов. Следовательно, дереву, состоящему из миллиона элементов, будет необходимо [lg(1000001], или 20 уровней.
Поэтому мы рассчитываем, что наша подпрограмма будет использовать примерно 21000 байт стекового пространства.
while (node) {
if (node->left) printTree(node->left);
getNodeAsString(node, buffer);
puts(buffer);
node = node->right;
}
Но самая большая выгода возникает при назначении одного-единственного буфера, доступ к которому осуществляется при всех обращениях к printTree. Если передать этот буфер в виде параметра для рекурсивных обращений, то будет назначено всего 1000 байт вне зависимости от глубины рекурсии.
void printTreePrivate(const Node *node, char *buffer) {
if (node) {
printTreePrivate(node->!eft, buffer);
getNodeAsStringfnode, buffer);
puts(buffer);
printTreePrivate(node->right, buffer);
}
}
void newPrintTree(const Node *node) {
char buffer[1000];
printTreePrivate(node, buffer);
)
Мы добавили массив rate_lookup, инициализированный таким образом, что элементам, отличным от Texas, Ohio и Maine, присвоено значение 1. Этот подход облегчает добавление значений для других штатов в будущем. В зависимости от ожидаемой схемы использования мы могли бы сделать поле points также средством поиска в массиве.
rate = rate_lookup[state];
amt = base * rate;
calc = 2*basis(amt) + extra(amt)*1.05;
if (state == OHIO)
points = 2;
public class Shape {
private double size;
public Shape(double size) {
this.size = size;
}
public double getSize() {return size;)
}
public class Square extends Shape {
public Square(double size) {
super(size);
}
public double area() {
double size = getSize();
return size*size;
}
)
public class Circle extends Shape {
public Circle(double size) {
super(size);
}
public double area() {
double size = getSize();
return Math.PI*size*size/4.0;
}
}
// efc…
Мы бы предложили абстрагировать форму окна из самого класса Window.
public abstract class Shape {
//...
public abstract boolean overlaps (Shape s);
public abstract int getArea();
}
public class Window {
private Shape shape;
public Window(Shape shape) {
this.shape = shape;
...
}
public void setShape(Shape shape) {
this.shape = shape;
...
}
public boolean overlaps(Window w) {
return shape.overlaps(w.shape);
}
public int getArea() {
return shape.getArea();
}
}
Заметим, что в этом подходе мы предпочли делегирование созданию подклассов: окно не является «разновидностью» формы – окно «имеет» форму. Оно использует форму для выполнения своей работы. Вы убедитесь, что во многих случаях делегирование оказывается полезным для реорганизации.
Мы могли бы расширить этот пример, внедрив интерфейс Java, указывающий на методы, которые должны поддерживаться неким классом для поддержания функций формы. Эта удачная идея означает, что, когда вы расширяете принцип формы, компилятор предупредит вас о классах, которые вы затронули. Мы рекомендуем использовать интерфейсы подобным способом при делегировании всех функций какого-либо другого класса.
public static void main(String args[]) {
// Create the blender to test
dbc_ex blender = new dbc_ex();
// And test it according to the string on standard input
try {
int a;
char c;
while ((a = System.in.read())!= -1) {
с = (char)a;
if (Character.isWhitespace(c)) {
continue;
}
if (Character.is Digit(с)) {
blender.setSpeed(Character.digit(c, 10));
}
else {
switch (c) {
case 'F': blender.fi!l();
break;
case 'E': blender.empty();
break;
case 's': System.out.printlnfSPEED: " + blender.getSpeed());
break;
case 'f': System.out.println(<FULL> + blender.isFull());
break;
default: throw new RuntimeException(
"Unknown Test directive");
}
}
}
catch (java.io.lOException e) {
System.err.println("Tesf jig failed: " + e.getMessage());
}
System.err.println("Completed blending\n");
System.exit(0);
}
Затем появится сценарий оболочки для управления тестированием.
#!/bin/sh
CMD="java dbc.dbc_sx"
failcount=0
expect okay() {
if echo "$*" | $CMD #>/dev/null 2>&1
then
...
else
echo "FAILED! $*"
failcount='expr $failcount + 1'
fi
}
expect_fail() {
if echo "$*" | SCMD>/dev/null 2> &1
then
echo "FAILED! (Should have failed): $*"
failcount='expr $failcount + 1'
fi
}
report() {
if [$failcount -gt 0]
then
echo – e "\n\n*** FAILED $failcount TESTS\n"
exit 1 # In case we are part of something larger
else
exit 0 # In case we are part of something larger
fi
}
#
# Start the tests
#
expect_okay F12345678987654321OE # Should run thru
expect_fail F5 # Fails, speed too high
expect_fail 1 # Fails, empty
expect_fail F10E1 # Fails, empty
expect_fail F1238 # Fails, skips
expect_okay FE # Never turn on
expect_fail F1E # Emptying while running
expect_okay F10E # Should be ok
report # Report results
При тестировании проверяется, не имеют ли место недопустимые переходы в скорости вращения ротора, если вы пытаетесь опорожнить работающий блендер, и т. д. Мы помещаем эту процедуру в сборочный файл, так что можно провести компиляцию и запустить регрессионный тест, просто введя команду:
% make % make test
Обратите внимание на то, что мы выходим из процедуры тестирования с 0 иди 1, так что можем использовать это и как часть более обширного теста.
В требованиях ничего не говорилось о запуске данного компонента через сценарий или даже с использованием языка. Конечные пользователи этого не увидят. Но у нас есть мощный инструмент, который можно использовать для быстрого и исчерпывающего тестирования нашей программы.
2. Это может быть корпоративным стандартом, но не требованием. Его лучше сформулировать так: "Фон диалогового окна должен настраиваться конечным пользователем. При поставке заказчику цвет будет серым". Еще лучше было бы сформулировать его более широко: "Все визуальные элементы приложения (цвета, шрифты и языки) должны настраиваться конечным пользователем".
3. Эта формулировка не является требованием, это архитектура. Когда вы сталкиваетесь с подобным, вам придется копать очень глубоко, чтобы понять, что же думает пользователь.
4. Основное требование, вероятно, выглядит примерно так: "Система предотвращает ввод пользователем недопустимых значений в поля и предупреждает пользователя, если ввод этих значений имеет место".
5. Эта формулировка, по всей вероятности, является жестким требованием.
Решение головоломки с четырьмя точками, приведенной в разделе "Разгадка невероятных головоломок".
Упражнение 22 из раздела "Балансировка ресурсов"
Ответ: В большинстве реализаций языков С и С++ отсутствуют способы проверки того, что указатель действительно указывает на допустимый блок памяти. Обычная ошибка состоит в освобождении блока памяти и организации ссылки на этот блок далее в тексте программы. К тому времени этот блок памяти уже может быть перераспределен для других целей. Обнуляя указатель, программисты надеются предотвращать эти инородные ссылки – в большинстве случаев разыменование указателя null генерирует ошибку в ходе выполнения программы.Упражнение 23 из раздела "Балансировка ресурсов"
Ответ: Обнуляя ссылку, вы уменьшаете число указателей на упомянутый объект на единицу. Как только этот счетчик становится равным нулю, объект получает право на сбор «мусора». Обнуление ссылок может играть существенную роль в продолжительных по времени программах, где программистам приходиться удостоверяться, что использование памяти со временем не возрастает.Упражнение 24 из раздела "Несвязанность и закон Деметера"
Ответ: Файл заголовка предназначен для определения интерфейса между соответствующей реализацией и внешним миром. Сам по себе файл заголовка не обязан обладать информацией о внутренней организации класса Date – от него лишь требуется сообщить компилятору о том, что конструктор принимает класс Date в качестве параметра. Поэтому, если файл заголовка не использует Dates в подставляемых функциях, второй фрагмент будет работать просто замечательно.А что же с первым фрагментом? Если он используется в небольшом проекте, то все нормально, за исключением того, что вы без особой надобности заставляете все элементы программы, которые используют класс Person1, также включать файл заголовка для класса Date. Как только подобное употребление становится обычной практикой в неком проекте, вскоре обнаружите, что включение одного файла заголовка заканчивается включением большей части системы, что существенно увеличивает время компиляции.
Упражнение 25 из раздела "Несвязанность и закон Деметера"
Ответ: Переменная acct передается в виде параметра, так что вызов getBalance является допустимым. Вызов amt.printFormat() таковым не является. Мы не «владеем» amt, и он не был передан нам. Мы могли устранить связывание showBalance с Money при помощи вставки, подобной представленной ниже:void showBalance(BankAccount b) {
b.printBalance();
}
Упражнение 26 из раздела "Несвязанность и закон Деметера"
Ответ: Поскольку класс Colada создает и владеет myBlender и myStuff, то обращения к addlngredients и elements являются допустимыми.Упражнение 27 из раздела "Несвязанность и закон Деметера"
Ответ: В этом случае processTransaction владеет amt – он создается на стеке. Происходит передача acct, поэтому допустимыми являются как setValue, так и setBalance. Но processTransaction не владеет who, поэтому вызов who->name() является нарушением. Закон Деметера предлагает заменить эту строку на следующую:markWorkflow(acct.name (), SET_BALANCE);
Программе в processTransaction не придется узнавать, какой дочерний объект в пределах BankAccount носит это имя – эта информация о структуре не должна разглашаться через контракт BankAccount. Вместо этого мы запрашиваем у BankAccount имя на счете. Он знает, где хранится имя (может быть, в объекте Person, в объекте Business, или в полиморфном объекте Customer).
Упражнение 28 из раздела "Метапрограммирование"
Ответ: Здесь не приводятся категорические ответы – вопросы предназначались в основном для того, чтобы дать вам пищу для размышлений. И вот что мы думаем:1. Назначения коммуникационного порта. Ясно, что эта информация должна сохраняться в виде метаданных. Но на каком уровне детализации? Некоторые коммуникационные программы системы Windows позволяют выбирать только скорость в бодах и порт (скажем, СОМ1 – COM4). Но вероятно вам придется указать размер слова, четность, стоповые биты, и настройку дуплексной связи. Старайтесь допускать самый мелкий уровень детализации, там где это разумно с практической точки зрения.
2. Поддержка выделения синтаксических конструкций различных языков в программе редактирования. Она должна быть реализована в виде метаданных. Вы же не хотите, чтобы вам пришлось переделывать программу только потому, что в последней версии языка Java было введено новое ключевое слово.
3. Поддержка редактора для различных графических устройств. Эту поддержку было бы трудно реализовать исключительно в виде метаданных. Вам не хотелось бы нагружать приложение многими драйверами устройств только для того, чтобы выбрать один из них во время выполнения программы. Однако вы могли воспользоваться метаданными для указания имени драйвера и динамической загрузки программы. Это еще один аргумент для сохранении метаданных в удобочитаемом формате; если вы используете программу для установки дисфункционального видеодрайвера, то вы не сможете переустановить его, пользуясь этой программой.
4. Конечный автомат для программы синтаксического анализа или сканера.
Это зависит от того, анализируете вы или просматриваете. Если анализируете некоторые данные, которые жестко определены в стандартах и скорее всего не будут изменены без одобрения конгресса США, то жесткое кодирование вполне годится. Но если вы сталкиваетесь с более изменчивой ситуацией, то может быть, более выгодным является внешнее определение таблиц состояний.
5. Типовые значения и результаты, используемые в модульном тестировании.
Большинство приложений определяет эти значения как встроенные в тестовый стенд, но вы можете добиться большей гибкости, перемещая тестовые данные и определение приемлемых результатов за пределы самой программы.
Упражнение 29 из раздела "Всего лишь представление"
Ответ: В программу Flight мы добавим ряд дополнительных методов душ поддержания двух списков «слушателей»: для уведомления о листе ожидания и о полной загрузке рейса.public interface Passenger {
public void waitListAvailable();
}
public interface Flight {
...
public void addWaitListListener(Passenger p);
public void removeWaitUstUstener(Passenger p);
public void addFullListener(FullListener b);
public void removeFullListener(FullListener b);
...
}
public interface BigReport extends FullListener {
public void FlightFullAlert(Flight f);
}
При неудачной попытке добавить Passenger, поскольку рейс полностью забронирован, мы можем (как вариант) поместить Passenger в лист ожидания. При открытии вакансии производится вызов метода waitListAvailable. Затем этот метод может осуществить выбор: либо добавить Passenger автоматически, либо дать указание сотруднику авиакомпании позвонить заказчику и выяснить, заинтересован ли он еще в рейсе, и т. п. Теперь мы обладаем достаточной гибкостью, чтобы избрать линию поведения, исходя из пожеланий клиента.
Кроме того, мы хотим избежать ситуаций, при которых BigReport разбирает тонны записей, отыскивая полностью забронированные рейсы. Зарегистрировав BigReport в качестве «слушателя» Flights, каждый индивидуальный Flight может сообщать, когда он полностью (или почти полностью) забронирован. Теперь пользователи могут мгновенно получить оперативные, с точностью до минуты, сообщения из BigReport, а не ожидать часами окончания его работы, как это было раньше.
Упражнение 30 из раздела "Доски объявлений"
Ответ:1. Обработка изображения.Для простого распределения рабочей нагрузки между параллельными процессами более чем адекватной может оказаться общедоступная очередь работ. Вы можете рассмотреть систему "доска объявлений" при наличии обратной связи, т. е. если результаты обработки одного фрагмента изображения влияют на другие фрагменты так, как это происходит в системах искусственного зрения или сложных трехмерных преобразованиях изображений.
2. Календарное планирование для групп.Для этого "доска объявлений" очень даже пригодится. Вы можете поместить назначенные собрания и готовность на "доску объявлений". Есть объекты, функционирующие автономно; в данном случае очень важна обратная связь, а участники могут приходить и уходить.
Можно рассмотреть возможность разделения "доски объявлений" в зависимости от того, кто осуществляет поиск: младший персонал может заботиться только о локальном офисе, отдел кадров интересоваться только англо-говорящими офисами во всем мире, а исполнительный директор – всем сразу.
В форматах данных имеется некий элемент гибкости: мы можем проигнорировать форматы или языки, которых не понимаем. Нам придется понимать различные форматы только для тех офисов, которые встречаются друг с другом, и не придется подвергать всех участников полному транзитивному замыканию всевозможных форматов. При этом связанность уменьшается там, где нужно, и мы не имеем искусственных ограничений.
3. Средство мониторинга компьютерной сети.Это весьма сходно с программой обработки заявлений на ипотечный кредит/ссуду, описанной в примере приложения (с. 153). На доску помещаются сообщения о неисправностях, присылаемые пользователями, и автоматические генерируемые статистические данные. Сотрудник (или программный агент) может анализировать "доску объявлений", чтобы осуществлять диагностику неисправностей в сети: две ошибки в линии могут быть отнесены на счет космических лучей, но 20000 ошибок говорят о проблеме в аппаратном обеспечении. Подобно детективам, разгадывающим тайну убийства, вы можете использовать множественные объекты, анализируя и внося свою лепту в решение проблем, связанных с компьютерной сетью.
Упражнение 31 из раздела "Программирование в расчете на стечение обстоятельств"
Ответ: Эта программа представляет ряд потенциальных проблем. Во-первых, она предлагает наличие текстовой среды. Это, может быть, и прекрасно, если предположение истинно, но что, если эта программа вызывается из графической среды, где не открыты ни stderr, ни stdin?Во-вторых, есть проблематичный оператор gets, который будет записывать столько символов, сколько он получит в переданный буфер. Злонамеренные пользователи использовали это, когда им не удавалось проделать бреши типа buffer overrun в защите многих различных систем. Никогда не пользуйтесь gets().
В-третьих, программа предполагает, что пользователь понимает английский язык.
И наконец, никто, находясь в здравом уме, не станет прятать средство взаимодействия с пользователем в недра библиотечной подпрограммы.
Упражнение 32 из раздела "Программирование в расчете на стечение обстоятельств"
Ответ: Работа программы strcpy в системе POSIX не гарантируется при наличии перекрывающихся строк. С некоторыми архитектурами она, случается, и работает, но лишь при стечении определенных обстоятельств.Упражнение 33 из раздела "Программирование в расчете на стечение обстоятельств"
Ответ: Она не будет работать в контексте апплета при наличии ограничений доступа по записи на локальный диск. Если вы можете выбирать, работать ли через графический интерфейс или нет, то, вероятно, захотите осуществить динамический анализ текущей среды. В этом случае вы наверняка захотите поместить файл журнала вне локального диска, если к нему нет доступа.Упражнение 34 из раздела "Скорость алгоритма"
Ответ: Ясно, что мы не можем давать никаких абсолютных ответов к этому упражнению. Однако можем дать вам пару намеков.Если ваши результаты не ложатся на гладкую кривую, то вы захотите проверить, не используется ли мощность вашего процессора каким-либо другим процессом. По всей вероятности, вы не получите хороших показателей в многопользовательской системе, и даже если окажетесь единственным пользователем, можете заметить, что фоновые процессы периодически отбирают циклы у ваших программ. Вы также можете захотеть проверить использование памяти: если приложение начинает использовать область свопинга, то производительность резко снижается.
Интересно поэкспериментировать с различными компиляторами и установочными параметрами оптимизации. Мы обнаружили, что весьма впечатляющее ускорение стало возможно, благодаря использованию агрессивной оптимизации. Мы также обнаружили, что на более распространенных архитектурах типа RISC компиляторы фирмы изготовителя часто превосходили по быстродействию более переносимую GCC. Возможно, изготовитель посвящен в тайны эффективной генерации программ на этих машинах.
Упражнение 35 из раздела "Скорость алгоритма"
Ответ: Программа printTree использует приблизительно 1000 байт стекового пространства для буферной переменной. Для движения вниз по древовидной схеме она рекурсивно вызывает саму себя, и каждый вложенный вызов добавляет еще 1000 байт к стеку. Она также вызывает саму себя, когда добирается до вершин, но заканчивает работу сразу, как только обнаружит, что переданный указатель обнулен. Если глубина дерева равна D, то максимальный объем, необходимый стеку, составляет (грубо) 1000 x(D+ 1).Сбалансированное двоичное дерево содержит вдвое больше элементов на каждом уровне. Дерево глубиной D содержит 1+2+4+8 +… +2^(D-1), или 2^D – 1 элементов. Следовательно, дереву, состоящему из миллиона элементов, будет необходимо [lg(1000001], или 20 уровней.
Поэтому мы рассчитываем, что наша подпрограмма будет использовать примерно 21000 байт стекового пространства.
Упражнение 36 из раздела "Скорость алгоритма"
Ответ: На ум приходит несколько процедур оптимизации. Программа printTree вызывает саму себя на вершинах дерева лишь для того, чтобы закончить работу при отсутствии потомков. Этот вызов увеличивает максимальную глубину стека примерно на 1000 байт. Можно также исключить рекурсию хвоста (второй рекурсивный вызов), хотя это и не будет затрагивать использование стека в наихудшем случае.while (node) {
if (node->left) printTree(node->left);
getNodeAsString(node, buffer);
puts(buffer);
node = node->right;
}
Но самая большая выгода возникает при назначении одного-единственного буфера, доступ к которому осуществляется при всех обращениях к printTree. Если передать этот буфер в виде параметра для рекурсивных обращений, то будет назначено всего 1000 байт вне зависимости от глубины рекурсии.
void printTreePrivate(const Node *node, char *buffer) {
if (node) {
printTreePrivate(node->!eft, buffer);
getNodeAsStringfnode, buffer);
puts(buffer);
printTreePrivate(node->right, buffer);
}
}
void newPrintTree(const Node *node) {
char buffer[1000];
printTreePrivate(node, buffer);
)
Упражнение 37 из раздела "Скорость алгоритма"
Ответ: Это можно сделать двумя путями. Один из них заключается в том, чтобы перевернуть проблему с ног на голову. Если в массиве есть лишь один элемент, мы не осуществляем итерации в цикле. Каждая дополнительная итерация удваивает размер массива, в котором можно осуществлять поиск. Отсюда общая формула размера массива: n = 2^m, где m – число итераций. Если прологарифмировать обе части с основанием 2, получим выражение lg(n) = lg(2^m), которое из определения логарифма превращается в lg(n) =m.Упражнение 38 из раздела "Реорганизация"
Ответ: Здесь мы могли бы предложить весьма умеренную реструктуризацию: убедитесь, что каждый тест выполняется лишь один раз, и сделайте все вычисления стандартными. Если выражение 2*basis (…)* 1.05 появляется в других местах программы, то, вероятно, его стоит сделать функцией. В данном случае мы этого делать не стали.Мы добавили массив rate_lookup, инициализированный таким образом, что элементам, отличным от Texas, Ohio и Maine, присвоено значение 1. Этот подход облегчает добавление значений для других штатов в будущем. В зависимости от ожидаемой схемы использования мы могли бы сделать поле points также средством поиска в массиве.
rate = rate_lookup[state];
amt = base * rate;
calc = 2*basis(amt) + extra(amt)*1.05;
if (state == OHIO)
points = 2;
Упражнение 39 из раздела "Реорганизация"
Ответ: Когда вы видите, что кто-либо использует перечислимые типы (или эквивалентные им в языке Java), для того чтобы провести различие между вариантами некоего типа, во многих случаях вы можете улучшить программу за счет создания подклассов:public class Shape {
private double size;
public Shape(double size) {
this.size = size;
}
public double getSize() {return size;)
}
public class Square extends Shape {
public Square(double size) {
super(size);
}
public double area() {
double size = getSize();
return size*size;
}
)
public class Circle extends Shape {
public Circle(double size) {
super(size);
}
public double area() {
double size = getSize();
return Math.PI*size*size/4.0;
}
}
// efc…
Упражнение 40 из раздела "Реорганизация"
Ответ: Этот случай интересен. На первый взгляд, кажется разумным, что у окна должна быть ширина и высота. Однако стоит подумать о будущем. Представим, что мы хотим обеспечить поддержку окон произвольной формы (что будет весьма трудно, если класс Window обладает всей информацией о прямоугольниках и их свойствах).Мы бы предложили абстрагировать форму окна из самого класса Window.
public abstract class Shape {
//...
public abstract boolean overlaps (Shape s);
public abstract int getArea();
}
public class Window {
private Shape shape;
public Window(Shape shape) {
this.shape = shape;
...
}
public void setShape(Shape shape) {
this.shape = shape;
...
}
public boolean overlaps(Window w) {
return shape.overlaps(w.shape);
}
public int getArea() {
return shape.getArea();
}
}
Заметим, что в этом подходе мы предпочли делегирование созданию подклассов: окно не является «разновидностью» формы – окно «имеет» форму. Оно использует форму для выполнения своей работы. Вы убедитесь, что во многих случаях делегирование оказывается полезным для реорганизации.
Мы могли бы расширить этот пример, внедрив интерфейс Java, указывающий на методы, которые должны поддерживаться неким классом для поддержания функций формы. Эта удачная идея означает, что, когда вы расширяете принцип формы, компилятор предупредит вас о классах, которые вы затронули. Мы рекомендуем использовать интерфейсы подобным способом при делегировании всех функций какого-либо другого класса.
Упражнение 41 из раздела "Программа, которую легко тестировать"
Ответ: Вначале добавим подпрограмму main, которая будет действовать как ведущий элемент модульного тестирования. В качестве аргумента она примет простой мини-язык: <Е> будет означать опорожнение блендера, <F> – его наполнение, цифры 0–9 будут задавать скорость вращения ротора, и т. д.public static void main(String args[]) {
// Create the blender to test
dbc_ex blender = new dbc_ex();
// And test it according to the string on standard input
try {
int a;
char c;
while ((a = System.in.read())!= -1) {
с = (char)a;
if (Character.isWhitespace(c)) {
continue;
}
if (Character.is Digit(с)) {
blender.setSpeed(Character.digit(c, 10));
}
else {
switch (c) {
case 'F': blender.fi!l();
break;
case 'E': blender.empty();
break;
case 's': System.out.printlnfSPEED: " + blender.getSpeed());
break;
case 'f': System.out.println(<FULL> + blender.isFull());
break;
default: throw new RuntimeException(
"Unknown Test directive");
}
}
}
catch (java.io.lOException e) {
System.err.println("Tesf jig failed: " + e.getMessage());
}
System.err.println("Completed blending\n");
System.exit(0);
}
Затем появится сценарий оболочки для управления тестированием.
#!/bin/sh
CMD="java dbc.dbc_sx"
failcount=0
expect okay() {
if echo "$*" | $CMD #>/dev/null 2>&1
then
...
else
echo "FAILED! $*"
failcount='expr $failcount + 1'
fi
}
expect_fail() {
if echo "$*" | SCMD>/dev/null 2> &1
then
echo "FAILED! (Should have failed): $*"
failcount='expr $failcount + 1'
fi
}
report() {
if [$failcount -gt 0]
then
echo – e "\n\n*** FAILED $failcount TESTS\n"
exit 1 # In case we are part of something larger
else
exit 0 # In case we are part of something larger
fi
}
#
# Start the tests
#
expect_okay F12345678987654321OE # Should run thru
expect_fail F5 # Fails, speed too high
expect_fail 1 # Fails, empty
expect_fail F10E1 # Fails, empty
expect_fail F1238 # Fails, skips
expect_okay FE # Never turn on
expect_fail F1E # Emptying while running
expect_okay F10E # Should be ok
report # Report results
При тестировании проверяется, не имеют ли место недопустимые переходы в скорости вращения ротора, если вы пытаетесь опорожнить работающий блендер, и т. д. Мы помещаем эту процедуру в сборочный файл, так что можно провести компиляцию и запустить регрессионный тест, просто введя команду:
% make % make test
Обратите внимание на то, что мы выходим из процедуры тестирования с 0 иди 1, так что можем использовать это и как часть более обширного теста.
В требованиях ничего не говорилось о запуске данного компонента через сценарий или даже с использованием языка. Конечные пользователи этого не увидят. Но у нас есть мощный инструмент, который можно использовать для быстрого и исчерпывающего тестирования нашей программы.
Упражнение 42 из раздела "Ошибка в определении требований" Ответ:
1. Эта инструкция похожа на реальное требование: имеются ограничения, налагаемые на приложение со стороны операционной среды.2. Это может быть корпоративным стандартом, но не требованием. Его лучше сформулировать так: "Фон диалогового окна должен настраиваться конечным пользователем. При поставке заказчику цвет будет серым". Еще лучше было бы сформулировать его более широко: "Все визуальные элементы приложения (цвета, шрифты и языки) должны настраиваться конечным пользователем".
3. Эта формулировка не является требованием, это архитектура. Когда вы сталкиваетесь с подобным, вам придется копать очень глубоко, чтобы понять, что же думает пользователь.
4. Основное требование, вероятно, выглядит примерно так: "Система предотвращает ввод пользователем недопустимых значений в поля и предупреждает пользователя, если ввод этих значений имеет место".
5. Эта формулировка, по всей вероятности, является жестким требованием.
Решение головоломки с четырьмя точками, приведенной в разделе "Разгадка невероятных головоломок".