Еще одно преимущество извлечения магического числа в качестве константы дает возможность четко документировать деловую информацию.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
Злоупотребление статическими классами можно считать плохой практикой. Но так же может злоупотреблять любой языковой особенностью.
Я не делаю различий между нестатическим классом, содержащим только статические методы, и статическим классом. По сути, это одно и то же, за исключением того, что статические классы позволяют компилятору реализовывать намерения разработчиков (отсутствие создания экземпляров этого класса, удобный синтаксис для доступа к его функциям, и т. Д. ).
Как вы говорите, распространение классов «Помощник» может привести к неприятностям (дизайн, ремонтопригодность, удобочитаемость, обнаруживаемость, другие способности ...). Здесь нет аргументов. Но можете ли вы утверждать, что класс «Помощник» никогда не подходит? Я сомневаюсь в этом.
Действительно, ответственное использование статических классов может иметь большие преимущества для вашего кода:
Enumerable
предоставляет набор методов расширения, которые полюбили большинство из нас. Это логический набор функциональности / бизнес-логики, который не связан ни с каким конкретным типом. Так что нет Вообще это не плохая практика. Просто используйте их с умом ...
Я думаю, что общий аргумент против сохранения изменяемого состояния в статических классах и использования их в качестве глобальных переменных. Нет ничего плохого в статических вспомогательных методах в статическом классе.
А что касается того, почему глобальные переменные плохие, я уверен, что здесь и в Google вы найдете полмиллиона страниц и постов об этом.
Вот некоторые ссылки на некоторые сообщения в блоге, посвященные тестированию Google, о том, почему статические методы могут повредить тестируемости вашего кода, а также почему статические методы (которые являются плохой частью статических классов) вводят все виды потенциально неожиданных связей и взаимодействий. между компонентами.
Существуют потребности, когда у вас есть класс Utility, в котором все методы являются статическими. В этом случае, если вы сделаете класс статичным, это ясно указывает на ваше намерение. И, по крайней мере, в C # нельзя использовать нестатические методы внутри статического класса.
Я все время использую статические служебные классы в своем коде для методов, которые вызываются довольно часто, но было бы неудобно для реализации. Примером может служить простой класс журналирования, например:
public static class Logging
{
public static UpdateAction(int id, object data)
{
SqlConnection connection = new SqlConnection("conn string from config file");
// more logic here...
}
}
Теперь я никоим образом не могу, чтобы эти классы и методы хранили какие-либо глобальные состояния, так как это может привести к огромным проблемам параллелизма и в целом просто плохой дизайн.
Поэтому не избегайте статических классов, просто избегайте, чтобы эти статические классы сохраняли какое-то глобальное состояние.
Нет, это не совсем правильно
Есть множество функций, которые не относятся к экземпляру объекта и не требуют состояния. Например, рассмотрим «математические» функции.
Почти во всех языках есть что-то вроде:
y = Math.round(x);
Таким образом, «круглая» функция статична. Если бы вы были сумасшедшими, вы могли бы спорить с чем-то вроде (C #):
class RoundFunction<T> : GeneralMathFunction<T>
{
public override T Operate (params object[] variables) {
..
}
}
Но, IMHO, это было бы немного странно.
Конечно, бывает время, когда слишком много статических функций является признаком что-то пошло не так, но в разумных пределах это не «плохо».
Присоединиться к рейтингу Время на одноразовом ранге, чтобы получить разрыв:
with cte_ranked as (
select *, row_number() over (partition by UserId order by Time) as rn
from table)
select l.*, datediff(minute, r.Time, l.Time) as gap_length
from cte_ranked l join cte_ranked r on l.UserId = r.UserId and l.rn = r.rn-1
Затем вы можете использовать множество методов, чтобы определить максимальный разрыв, когда он начался и т. Д.
Обновление
Мой первоначальный ответ был написан из Mac без базы данных для тестирования. У меня было еще немного времени, чтобы поиграть с этой проблемой и фактически протестировать и измерить, как она работает на таблице 1M записей. Моя тестовая таблица определена следующим образом:
create table access (id int identity(1,1)
, UserId int not null
, Time datetime not null);
create clustered index cdx_access on access(UserID, Time);
go
Для выбора записи для любой информации я пока предпочитаю следующий ответ:
with cte_gap as (
select Id, UserId, a.Time, (a.Time - prev.Time) as gap
from access a
cross apply (
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc) as prev)
, cte_max_gap as (
select UserId, max(gap) as max_gap
from cte_gap
group by UserId)
select g.*
from cte_gap g
join cte_max_gap m on m.UserId = g.UserId and m.max_gap = g.gap
where g.UserId = 42;
Из 1M записи, ~ 47k различных пользователей, результат для этого возвращается в 1 мс в моем тесте маленький экземпляр (теплый кеш), чтение 48 страниц.
Если фильтр UserId = 42 удален, максимальный промежуток и время, когда оно произошло для каждого пользователя (с дубликатами для нескольких максимальных промежутков), потребуют 6379139 чтений, довольно много и занимает 14 секунд на моей тестовой машине.
Время можно сократить вдвое, если требуются только UserId и максимальный разрыв (нет информации , когда максимальный разрыв произошел):
select UserId, max(a.Time-prev.Time) as gap
from access a
cross apply (
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc
) as prev
group by UserId
Для этого требуется только 3193448 чтений, только половина по сравнению с предыдущим, и завершился за 6 секунд на 1 млн записей. Разница возникает из-за того, что предыдущей версии необходимо было оценить каждый пробел один раз, чтобы найти максимальный, а затем снова оценить их, чтобы найти те, которые равны максимальному. Обратите внимание, что для этих результатов производительности структура таблицы, которую я предложил с индексом на (UserId, Time), является критической .
Что касается использования CTE и «разделов» (более известных как функции ранжирования) ): это все ANSI SQL-99 и поддерживается большинством поставщиков. Единственной конструкцией, специфичной для SQL Server, было использование функции dateiff
, которая теперь удалена. У меня такое чувство, что некоторые читатели понимают термин «агностик» как «SQL с наименьшим общим знаменателем, который понимает и мой любимый поставщик». Также обратите внимание, что использование общих табличных выражений и оператора перекрестного применения используется исключительно для улучшения читаемости запроса. Оба могут быть заменены производной таблицей с помощью простой механической замены. Вот тот же самый запрос , в котором CTE заменены производными таблицами. Я позволю вам судить о его удобочитаемости по сравнению с версией, основанной на CTE:
select g.*
from (
select Id, UserId, a.Time, (a.Time - (
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc
)) as gap
from access a) as g
join (
select UserId, max(gap) as max_gap
from (
select Id, UserId, a.Time, (a.Time - (
select top(1) Time
from access b
where a.UserId = b.UserId
and a.Time > b.Time
order by Time desc
)) as gap
from access a) as cte_gap
group by UserId) as m on m.UserId = g.UserId and m.max_gap = g.gap
where g.UserId = 42
Черт, я прыгал, в итоге получится более запутанная лол. Это вполне читаемо, потому что у него было только два CTE для начала. Тем не менее, в запросах с 5-6 производными таблицами форма CTE намного более читабельна.
Для полноты, вот то же преобразование, примененное к моему упрощенному запросу (только максимальные пробелы,