Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Кристиан Гросс - C# 2008

.pdf
Скачиваний:
124
Добавлен:
12.03.2015
Размер:
6.72 Mб
Скачать

Работа

со строками

81

пешной, т. к. метод CompareTo () сбивает с толку смещение буфера, вызванное начальной буквой "а".

Но второе решение, нахождение подстроки, успешно проходит испытание новым тестом, который находит слово "alio". Так как первое решение не выдерживает новый тест, то можно сказать, что данное решение неприемлемо. В то же самое время успешное прохождение вторым решением второго теста вдобавок к первому повышает наше доверие этому решению. Но не стоит торопиться делать заключение касательно его надежности, т. к. оно не выдерживает испытания следующим тестовым кодом:

verifyValue = FindSubstring("allodium");

if (verifyValue.CompareTo("hallo") != 0) {

Console.WriteLineC'Test failed: cannot parse multiple words");

}

Проверяемое слово, "allodium", содержит символы "alio". Верификация будет успешной, когда она не должна быть таковой, что является примером ложноположительного результата.

ПРИМЕЧАНИЕ

Важно иметь большое количество тестов, чтобы можно было проверить как можно больше возможных сценариев. Эти тесты должны содержать такие, которые должны вызывать успешное выполнение, и такие, которые должны вызывать неудачное выполнение.

Конечный вывод таков, что ни одно из решений не работает должным образом. Подвергнув оба решения расширенному тестированию, мы обнаружили сценарии, вызывающие неуспешное исполнение каждого из них. Таким образом, нам необходимо найти новое решение проблемы пробельных символов.

САМОИСТЯЗАНИЕ ПРИ РАЗРАБОТКЕ

С первого взгляда может показаться, что создание решений, следуемое созданием тестов, которые сводят на нет эти решения, является упражнением в самоистязании. Еще бы — вы намеренно стараетесь завалить код, в создание которого вложили столько усилий. Но вы должны осознавать, что этот процесс является частью общего процесса разработки программного обеспечения, и обязаны относиться к нему со всей серьезностью. Некоторые программисты не заботятся о создании тестов для своего кода, но это те программисты, которые создают профессии разработчика программного обеспечения дурную славу.

Вы должны стремиться быть разработчиком, которому можно доверять, а для этого необходимо тестировать свой код. Я работал, а моя жена и сейчас работает, руководителем проектов по разработке прогрвммного обеспечения. В ее словах: "Я могу терпеть медленных разработчиков, но я не выношу разработчиков, которым я не могу доверять в написании стабильного и надежного кода. Проблема с ненадежными разработчиками заключается в том, что я не могу позволить им отправить код в промышленный выпуск, и мне всегда нужно иметь кого-либо, чтобы присматривать за их работой".

82

Глава 10

Пишем тест, прежде чем писать код

Предыдущие решения были неудачными, потому что каждое из них было своего рода коленным рефлексом. В программировании коленный рефлекс — это создание решения, направленного на исправление обнаруженной ошибки, и не более того. Вместо этого подхода следует вычислить, о чем говорит нам данная ошибка. Первоначальная проблема с ведущими пробельными символами не заключалась лишь в наличии пробельных символов, а была своего рода вопросом: "А что, если текст не выровнен или является частью предложения и т. п.?"

Чтобы исправить ошибку, мы не пишем код, но продумываем все тесты, которые наш код должен выдержать. Необходимо распределить ответственность и определить контексты успешного и неудачного исполнения кода. В примере с переводом, правильным подходом к реализации этого приложения было бы создание тестов до создания кода. Контексты успешного и неудачного исполнения для этого приложения представлены в табл. 3.1.

 

 

Таблица 3.1.

Ситуации для тестирования программы перевода

 

 

Ситуация

Результат верификации

 

 

 

 

a l i o

 

Успех

 

 

 

 

" a l i o "

Успех

 

 

 

 

word

a l i o

Неудача: нельзя переводить одно слово без перевода всех других

 

 

слов

 

 

 

 

word

a l i o word

Неудача: по той же само причине, что и в случае с текстом word

 

 

a l i o

 

 

 

p r e f i x a l l o

Неудача: не то слово

 

 

a l l o a p p e n d

Неудача: не то слово

 

 

p r e f i x a l l o a p p e n d

Неудача: не то слово

 

 

 

 

Как можно видеть, исполнение большинства тестов заканчивается неудачей, т. к. компонент перевода способен переводить только отдельные слова. Тесты в табл. 3.1 вроде бы охватывают все возможные ситуации, но в действительности это не так. Имеются еще две возможные ситуации, которые показаны в табл. 3.2.

Таблица

3.2. Дополнительные

ситуации

для

тестирования

 

 

 

 

 

Ситуация

 

Результат верификации

 

 

 

 

 

 

 

 

Alio

 

Успех

 

 

 

" alio "

 

Успех

 

 

 

 

 

 

 

 

 

Текст может быть в смешанном регистре, и с точки зрения людей такое слово равнозначно слову в одном регистре. Но компьютеры рассматривают текст в другом

Работа со строками

83

регистре как совсем иной текст, поэтому мы должны быть в состоянии обрабатывать такую ситуацию.

На рис. 3.9 показано рабочее решение проблемы.

Рис. 3.9. Конечное приложение перевода

Рассматривая решение, можно видеть, что оно содержит элементы из первого решения, которое было отброшено, потому что оно не работало должным образом. Это хороший пример того, как можно ошибиться, исправив по-быстрому ошибку и увидев, что это решение не работает, отбросить это решение. Поэтому необходимо сначала основательно подумать, почему код не работает должным образом, а не просто исправлять на скорую руку ошибку, лишь бы избавиться от нее.

ПРИМЕЧАНИЕ

Во всех рассмотренных решениях применялись методы типа s t r i n g . Это очень сложный тип, который поддерживает многие операции, часто применяющиеся на текстовых данных.

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

Заключение строк в кавычки

Вы, наверное, обратили внимание на использование двойных и одинарных кавычек в вызове метода CompareTo (). Эти два типа кавычек существенно отличаются друг от друга. Двойные кавычки определяют строковый тип, как показано в следующем примере:

"using double quotes"

Одинарные же кавычки применяются для определения символа, например 'а'.

84 Глава 10

Соответственно, одинарные кавычки можно использовать только с одиночным символом. Одиночный символ можно рассматривать как букву, но это не всегда так, поскольку не во всех языках имеются буквы. При попытке определить строку с помощью одинарных кавычек компилятор С# сгенерирует ошибку, которая в .NET обычно называется исключением (exception).

Кодовые таблицы символов

Для хранения одного символа требуется 16 бит памяти, а объем памяти, занимаемый строкой, зависит от количества символов в строке. Например, для хранения строки длиной в 10 символов требуется 160 бит памяти. Тип string является ссылочным типом.

Так как для хранения одного символа отводится 16 бит, то текст можно хранить в огромном разнообразии форматов. В данном случае применяется стандартный формат, называемый Unicode.

Возьмем, к примеру, букву а. По-философски, каким образом мы знаем, что а — это а? Для нас это не составляет особого труда, т. к. наш мозг натренирован взаимосвязывать очертания и весь внешний вид данной фигуры с концепцией буквы а. Теперь посмотрим на английскую букву, показанную на рис. 3.10.

Р

Рис. 3.10. Буква английского алфавита

Какая буква показана на рис. 3.10? Выглядит, как будто бы буква Р, не так ли? Но английская Р— это русская П. В каждом из этих двух языков применяется свой набор символов для обозначения букв, и английской букве Р соответствует русская П. Соответствие всех букв русского алфавита английскому показано в табл. 3.3.

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

пьютеры также нуждаются

в шпаргалке такого вида, т. к. они не понимают букв,

а только числа. Поэтому в

компьютерах применяются таблицы преобразований,

с помощью которых набор букв соотносится с набором чисел.

Существует несколько типов таблиц преобразований, одной из них является код ASCII (American Standard Code for Information Interchange, Американский стандартный код обмена информацией). Так, например, в ASCII английская буква а соотносится с числом 97. Но с ASCII имеется проблема — в то время как этот код прекрасно работает с английским алфавитом, с другими алфавитами он работает отвратительно. Код ASCII был расширен для работы с западноевропейскими языками, но с такими языками как китайский, русский или арабский у него имеются проблемы.

Работа со

строками

 

 

85

 

 

 

Таблица

3.3.

Соотношение

русских букв английским

 

 

 

 

 

 

 

Русский

 

 

Английская транс-

Русский

 

Английская транс-

 

 

 

литерация

 

 

 

литерация

 

 

 

 

 

 

 

 

а

 

 

а

с

 

 

s

 

 

 

 

 

 

 

 

б

 

 

b

т

 

 

t

 

 

 

 

 

 

 

 

в

 

 

V

У

 

 

U

 

 

 

 

 

 

 

 

г

 

 

g

ф

 

 

f

д

 

 

d

X

 

 

kh

 

 

 

 

 

 

 

 

е/ё

 

 

е

М

 

 

ts

 

 

 

 

 

 

 

 

ж

 

 

zh

ч

 

 

ch

 

 

 

 

 

 

 

 

3

 

 

z

ш

 

 

sh

 

 

 

 

 

 

 

 

и/й

 

 

1

щ

 

 

shch

к

 

 

k

ъ

 

 

ii

 

 

 

 

 

 

 

 

 

 

 

 

 

л

 

 

1

ы

 

 

У

 

 

 

 

 

 

 

 

м

 

 

m

ь

 

 

1

 

 

 

 

 

 

 

 

н

 

 

n

э

 

 

e

 

 

 

 

 

 

 

 

0

 

 

0

ю

 

 

iu

 

 

 

 

 

 

 

 

п

 

 

P

я

 

 

ia

 

 

 

 

 

 

 

 

Р

 

 

r

 

 

 

 

 

 

 

 

 

 

 

 

По этой причине в .NET применяется кодировка Unicode. Эта кодировка определяет набор таблиц преобразования, которые соотносят все алфавиты мира с определенным набором чисел.

В большинстве случаев вам не придется иметь дело с Unicode, т. к. все, связанное

скодировками, .NET выполняет прозрачно для программиста. Дело обстояло совсем по-другому много лет тому назад, когда программистам приходилось выполнять самостоятельно всю связанную с таблицами преобразований работу. Так что вы можете считать, что вам повезло: вам не придется познать всю радость такой работы при разработке многоязычных приложений.

Языки и региональные стандарты

При работе со строками в .NET применяется не только Unicode. Среда .NET очень инновационная в том смысле, что она понимает такие концепции, как региональ-

ные стандарты и

язык, которые являются отображением

того, как

люди живут

и разговаривают.

Концепция региональных стандартов и

языка не

существует

в других средах программирования.

 

 

Возьмем, к примеру, Швейцарию, страну размером чуть больше Московской области, расположенную в центре Европе. Швейцария — горная страна, население которой разделено горными хребтами на четыре лингвистические группы: немецкую.

4 Зак. 555

86

Глава 10

итальянскую, ретороманскую и французскую. Но, несмотря на то, что в стране четыре языка, швейцарцы используют одну валюту и пишут цифры одинаковым образом.

В предыдущих средах программирования, язык был привязан к определенной стране. Такое решение прекрасно подходит для Франции, Германии и Соединенных Штатов, но никуда не годится для Канады, Швейцарии, Бельгии и Индии. Язык необходимо рассматривать отдельно от страны, т. к. на одном и том же языке могут разговаривать в разных странах. Например, на итальянском разговаривают в Швейцарии и Италии, на французском — во Франции, Швейцарии, Люксембурге и Канаде, а на немецком — в Германии, Швейцарии и Австрии.

Установка региональных стандартов и языка в Windows

Операционная система Windows позволяет установить региональные стандарты и язык вашего компьютера, независимо от языка самой Windows. Пример установки региональных стандартов и языка показан на рис. 3.11.

Рис. 3.11. Установка региональных стандартов и языка в Windows

На рис. 3.11 показана Панель управления немецкой версии Windows на компьютере в Швейцарии. В системе установлен английский язык и швейцарские регио-

Работа

со

строками

87

нальные стандарты. Может показаться, что такое сочетание могло бы сбить Windows с толку, но поддержка, казалось бы, разных языков и региональных стандартов не представляет в Windows никаких проблем. Если приложение написано должным образом, то поддержка нескольких языков и региональных стандартов не составляет никакого труда.

Анализ и обработка чисел

Региональные стандарты и страна играют важную роль при обработке чисел и дат, которые хранятся в виде строк. Представьте себе выполнение сложения чисел, хранящихся в виде строк. Пример такого сложения продемонстрирован на рис. 3.12.

Рис. 3.12. Результаты арифметических операций с числами, хранящимися в виде строк, могут быть неожиданными

Сложение чисел представляет собой арифметическую операцию. Но при сложении строк выполняется операция конкатенации, т. е. сращивание одной строки с другой. Использование операции сложения со строками представляет удобный способ для конкатенации строк.

Но в данном примере конкатенация не была целью сложения. Чтобы получить желаемый результат, нам нужно обращаться со строками, как с числами, над которыми потом и выполняется операция сложения, а результат — 3 ( 1 + 2 = 3) — сохраняется в переменной с. Исправленный код для получения желаемого результата показан на рис. 3.13. Этот код использует метод .NET, чтобы сопоставить строковому представлению цифры соответствующее числовое представление.

Тип int имеет метод Parse о, с помощью которого можно преобразовать строковое представление числа в его числовой эквивалент. Метод работает должным образом, только если строка содержит допустимое представление числа. Иными словами, строка может содержать только цифры, но не буквы или, за исключением знака "плюс" или "минус", другие символы. В последнем случае метод Parse о выдает ошибку.

88

Глава 10

Рис. 3.13. Сопоставление строк числам

Если код не может справиться с неудачно завершившейся операцией преобразования строки, в процедурах анализа обычно генерируется исключение, которое может быть обработано программой. Другим способом безопасного преобразования строки в число без применения исключения является метод TryParse (), как показано в следующем примере:

int value;

i f(int.TryParse("1", out value)) С

}

Метод TryParse () возвращает не соответствующее целочисленное значение, а булев флаг, указывающий, поддается ли строка преобразованию. Если возвращается значение true, то тогда строка преобразуется, а результат сохраняется в параметре value, обозначенном идентификатором out. Идентификатор out указывает, что результаты проверки сохраняются в следующем за ним параметре: преобразованное значение в случае успеха или 0 в противном случае. С помощью метода TryParse () можно преобразовывать другие числовые типы, например float .TryParse ().

Можно также преобразовывать числа других систем счисления, скажем, шестнадцатеричной. Например, в следующем коде показано преобразование шестнадцатеричного числа 100 из строкового представления в числовое:

using System.Globalization;

public void ParseHexadecimal() {

int value = int. Parse (" 100" , NumberStyles .HexNumber) ;

}

В коде используется версия метода Parseo, которая имеет дополнительный параметр, указывающий формат преобразуемого числа. В данном случае второй параметр— NumberStyles.HexNumber ИЗ пространства имен System.Globalization—

указывает, что число в шестнадцатеричном формате.

Работа со строками

89

ПРИМЕЧАНИЕ

Убедиться в том, что 100 в шестнадцатеричном счислении соотносится с 256 в десятичной, можно с помощью программы Калькулятор, поставляемой с операционной системой Windows. Переключите калькулятор в инженерный вид, установите переключатель Hex, введите число 100 и установите переключатель обратно в Dec.

Перечисление NumberStyles содержит другие члены, которые можно использовать для преобразования чисел в соответствии с другими правилами. Например, член AiiowParentheses применяется для обработки скобок, обозначающих, что число отрицательное, а члены AiiowLeadingwhite и AiiowTraiiingwhite обрабатывают начальные и конечные пробельные символы соответственно. Пример использования этих членов перечисления NumberStyles приводится в следующем коде:

public void TestParseNegativeValue(){ int value = int.Parse(" (10)

NumberStyles.AllowParentheses |

NumberStyles.AllowLeadingWhite |

NumberStyles .AiiowTraiiingwhite)

}

В данном примере строковое представление числа 10 усложнено скобками, начальными и конечными пробельными символами. Применение только метода Parse () для преобразования этой строки в числовое представление не даст желаемых результатов, поэтому необходимо использовать члены перечисления NumberStyles. Член перечисления AllowParentheses обрабатывает скобки, AllowLeadingWhite — начальные пробельные символы, a AiiowTraiiingwhite— конечные пробельные символы. После обработки строки этими членами перечисления NumberStyles и методом Parse () в переменной value сохраняется числовое значение -10.

Другие члены перечисления NumberStyles применяются для обработки десятичной точки в дробных числах, положительных и отрицательных чисел и т. п. Это приводит нас к теме обработки чисел иных типов, чем целые (int). Каждый из базо-

вых ТИПОВ данных, таких как boolean, byte и double, имеет СВОИ методы Parse о

и TryParse)). Кроме этого, метод TryParse о может использовать перечисление NumberStyles. (Подробную информацию о членах перечисления см. в документации MSDN.)

Преобразование целочисленных значений выполняется одинаковым образом, независимо от страны. Но с преобразованиями действительных чисел и дат дело обстоит иначе. Рассмотрим следующий пример кода, в котором делается попытка преобразовать строку, содержащую представление десятичных значений:

public void TestDoubleValue() {

double value = double.Parse("1234.56");

value = double.Parse("1, 234. 56") ;

}

90

Глава 10

В данном примере оба случая применения метода Parse О обрабатывают число 1234.56. В первом случае метод Parse() преобразовывает простое число, т. к. оно содержит только десятичную точку, отделяющую целую часть от дробной. Во втором случае метод Parse о преобразует более сложное число, содержащее кроме десятичной точки разделитель тысяч. В обоих случаях процедуры Parse () исполнялись успешно.

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

Культурная среда

В .NET информация о культурной среде указывается с помощью двух идентификаторов: языка и региональных стандартов. Как было упомянуто ранее, в Швейцарии разговаривают на четырех языках. Это означает, что дата, время и денежная единица выражаются в четырех разных способах. Это не означает, что формат даты разный в немецком и французском языках. Но при одинаковом формате слова для обозначения марта — Maerz или Mars — будут разными. С другой стороны, слова для обозначения дат одинаковые в Австрии, Швейцарии и Германии, но формат даты разный. Это означает, что для стран с несколькими языками, например Канады (французский и английский) или Люксембурга (французский и немецкий), необходимо применение нескольких кодировок, отсюда и надобность в двух идентификаторах. Информацию о культурной среде можно извлечь с помощью следующего кода:

Culturelnfo info = Thread.CurrentThread.CurrentCulture();

Console.WriteLine("Culture (" + info.EnglishName + ")");

В данном примере информация о культурной среде, ассоциированной с текущим потоком, извлекается С помощью метода Thread.CurrentThread.CurrentCulture().

Как видно из этого примера, отдельные потоки можно ассоциировать с разными культурными средами. Свойство EngiishName генерирует английскую версию информации о культурной среде, которая, в случае установок, приведенных на рис. 3.11, была бы следующей:

Culture (English (Canada))

Теперь рассмотрим число 1,234.

В американских или канадских региональных стандартах это будет число тысяча двести тридцать четыре. Один из способов изменить региональные стандарты заключается в использовании диалогового окна языка и региональных стандартов (см. рис. 3.11). Но это можно также сделать программным образом, как показано в следующем коде:

Thread.CurrentThread.CurrentCulture = new Culturelnfo("en-CA");

В данном примере создается новый экземпляр класса Culturelnfo, содержащий региональный стандарт еп-СА.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]