Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Философия программирования > Каков нормальный объем процедуры?


Автор: TXC 9.6.2007, 01:19
Вот пишу вычислительную по Delphi. (Некогда писать и отлаживать универсальные процедуры и функций.) И ужасаюсь, что у меня некоторые процедуры выходят приличной длины. Вообще-то меня они не смущают, но где-то слышал, что лучше не растягивать (вмещаться до 20 строк) и писать по-короче, но у меня не всегда так получается.

Так например:
Код

procedure TfmDeleteRecords.sbDeleteRecordsClick(Sender: TObject);
var i : Integer;
begin
  {------------------------------------}
  { Якщо видаляємо записи користувачів }
  {------------------------------------}
  if fmDeleteRecords.cbChangeDB.Text='База користувачів' then
  begin
    for i:=1 to fmMain.sgUser.RowCount-1 do
    begin
      { Шукаємо I параметр. }
      if (leUserName.Text<>'') then
        { Якщо є такий I_параметр - ставимо мітку }
        if (pos(leUserName.Text, fmMain.sgUser.Cells[0, i])>0) then
          fmMain.sgUser.RowHeights[i]:=-ABS(fmMain.sgUser.RowHeights[i]);

      { Шукаємо II параметр. }
      if (leUserGroup.Text<>'') then
      begin
        { Якщо є такий II_параметр і I_параметр не шукали - ставимо мітку }
        if (pos(leUserGroup.Text, fmMain.sgUser.Cells[1, i])>0)
            and (leUserName.Text='') then
          fmMain.sgUser.RowHeights[i]:=-ABS(fmMain.sgUser.RowHeights[i]);
        { Якщо немає такого II_параметру, а попередній пошук знайшов - знімаємо мітку }
        if (pos(leUserGroup.Text, fmMain.sgUser.Cells[1, i])=0)
            and (fmMain.sgUser.RowHeights[i]<0) then
          fmMain.sgUser.RowHeights[i]:=ABS(fmMain.sgUser.RowHeights[i]);
      end;

      { Шукаємо III параметр. }
      if (leUserPassword.Text<>'') then
      begin
        { Якщо є такий III_параметр і I-II_параметри не шукали - ставимо мітку }
        if (pos(leUserPassword.Text, fmMain.sgUser.Cells[2, i])>0)
            and (leUserName.Text='')
            and (leUserGroup.Text='') then
          fmMain.sgUser.RowHeights[i]:=-ABS(fmMain.sgUser.RowHeights[i]);
        { Якщо немає такого III_параметру, а попередні пошуки знайшли - знімаємо мітку }
        if (pos(leUserPassword.Text, fmMain.sgUser.Cells[2,i])=0)
            and (fmMain.sgUser.RowHeights[i]<0) then
          fmMain.sgUser.RowHeights[i]:=ABS(fmMain.sgUser.RowHeights[i]);
      end;
    end;
  end;

  {------------------------------}
  { Якщо видаляємо записи питань }
  {------------------------------}
  if fmDeleteRecords.cbChangeDB.Text='База питань' then
  begin
    for i:=1 to fmMain.sgQuestion.RowCount-1 do
    begin
      { Шукаємо I параметр. }
      if (leQuestionName.Text<>'') then
        { Якщо є такий I_параметр - ставимо мітку }
        if (pos(leQuestionName.Text, fmMain.sgQuestion.Cells[0, i])>0) then
          fmMain.sgQuestion.RowHeights[i]:=-ABS(fmMain.sgQuestion.RowHeights[i]);

      { Шукаємо II параметр. }
      if (leQuestionTheme.Text<>'') and (leQuestionTheme.Enabled=True) then
      begin
        { Вибираємо метод оцінки < = > }
        { Якщо є такий II_параметр і I_параметр не шукали - ставимо мітку }
        if ((cbQuestionTheme.Text='<')
            and (fmMain.sgQuestion.Cells[1, i]<leQuestionTheme.Text)
            and (leQuestionName.Text=''))
            OR
            ((cbQuestionTheme.Text='=')
            and (fmMain.sgQuestion.Cells[1, i]=leQuestionTheme.Text)
            and (leQuestionName.Text=''))
            OR
            ((cbQuestionTheme.Text='>')
            and (fmMain.sgQuestion.Cells[1, i]>leQuestionTheme.Text)
            and (leQuestionName.Text=''))
              then
          fmMain.sgQuestion.RowHeights[i]:=-ABS(fmMain.sgQuestion.RowHeights[i]);
        { Якщо немає такого II_параметру, а попередній пошук знайшов - знімаємо мітку }
        if ((cbQuestionTheme.Text='<')
            and (fmMain.sgQuestion.Cells[1, i]>=leQuestionTheme.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
            OR
            ((cbQuestionTheme.Text='=')
            and (fmMain.sgQuestion.Cells[1, i]=leQuestionTheme.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
            OR
            ((cbQuestionTheme.Text='>')
            and (fmMain.sgQuestion.Cells[1, i]<=leQuestionTheme.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
              then
          fmMain.sgQuestion.RowHeights[i]:=ABS(fmMain.sgQuestion.RowHeights[i]);
      end;

      { Шукаємо III параметр. }
      if (leQuestionRightAnswer.Text<>'') and (leQuestionRightAnswer.Enabled=True) then
      begin
        { Вибираємо метод оцінки < = > }
        { Якщо є такий III_параметр і I-II_параметр не шукали - ставимо мітку }
        if ((cbQuestionRightAnswer.Text='<')
            and (fmMain.sgQuestion.Cells[2, i]<leQuestionRightAnswer.Text)
            and (leQuestionName.Text='')
            and (leQuestionTheme.Enabled=False))
            OR
            ((cbQuestionRightAnswer.Text='=')
            and (fmMain.sgQuestion.Cells[2, i]=leQuestionRightAnswer.Text)
            and (leQuestionName.Text='')
            and (leQuestionTheme.Enabled=False))
            OR
            ((cbQuestionRightAnswer.Text='>')
            and (fmMain.sgQuestion.Cells[2, i]>leQuestionRightAnswer.Text)
            and (leQuestionName.Text='')
            and (leQuestionTheme.Enabled=False))
              then
          fmMain.sgQuestion.RowHeights[i]:=-ABS(fmMain.sgQuestion.RowHeights[i]);
        { Якщо немає такого III_параметру, а попередній пошук знайшов - знімаємо мітку }
        if ((cbQuestionRightAnswer.Text='<')
            and (fmMain.sgQuestion.Cells[2, i]>=leQuestionRightAnswer.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
            OR
            ((cbQuestionRightAnswer.Text='=')
            and (fmMain.sgQuestion.Cells[2, i]=leQuestionRightAnswer.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
            OR
            ((cbQuestionRightAnswer.Text='>')
            and (fmMain.sgQuestion.Cells[2, i]<=leQuestionRightAnswer.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
              then
          fmMain.sgQuestion.RowHeights[i]:=ABS(fmMain.sgQuestion.RowHeights[i]);
      end;

      { Шукаємо IV параметр. }
      if (leQuestionPrice.Text<>'') and (leQuestionPrice.Enabled=True) then
      begin
        { Вибираємо метод оцінки < = > }
        { Якщо є такий IV_параметр і I-III_параметр не шукали - ставимо мітку }
        if ((cbQuestionPrice.Text='<')
            and (fmMain.sgQuestion.Cells[3, i]<leQuestionPrice.Text)
            and (leQuestionName.Text='')
            and (leQuestionTheme.Enabled=False)
            and (leQuestionRightAnswer.Enabled=False))
            OR
            ((cbQuestionPrice.Text='=')
            and (fmMain.sgQuestion.Cells[3, i]=leQuestionPrice.Text)
            and (leQuestionName.Text='')
            and (leQuestionTheme.Enabled=False)
            and (leQuestionRightAnswer.Enabled=False))
            OR
            ((cbQuestionPrice.Text='>')
            and (fmMain.sgQuestion.Cells[3, i]>leQuestionPrice.Text)
            and (leQuestionName.Text='')
            and (leQuestionTheme.Enabled=False)
            and (leQuestionRightAnswer.Enabled=False))
              then
          fmMain.sgQuestion.RowHeights[i]:=-ABS(fmMain.sgQuestion.RowHeights[i]);
        { Якщо немає такого IV_параметру, а попередній пошук знайшов - знімаємо мітку }
        if ((cbQuestionPrice.Text='<')
            and (fmMain.sgQuestion.Cells[3, i]>=leQuestionPrice.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
            OR
            ((cbQuestionPrice.Text='=')
            and (fmMain.sgQuestion.Cells[3, i]=leQuestionPrice.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
            OR
            ((cbQuestionPrice.Text='>')
            and (fmMain.sgQuestion.Cells[3, i]<=leQuestionPrice.Text)
            and (fmMain.sgQuestion.RowHeights[i]<0))
              then
          fmMain.sgQuestion.RowHeights[i]:=ABS(fmMain.sgQuestion.RowHeights[i]);
      end;
    end;
  end;

  {---------------------------}
  { Якщо видаляємо записи тем }
  {---------------------------}
  if fmDeleteRecords.cbChangeDB.Text='База тем' then
  begin
    for i:=1 to fmMain.sgTheme.RowCount-1 do
    begin
      { Шукаємо I параметр. }
      if (leThemeNumber.Text<>'') and (leThemeNumber.Enabled=True) then
      begin
        { Вибираємо метод оцінки < = > }
        { Якщо є такий I_параметр - ставимо мітку }
        if ((cbThemeNumber.Text='<')
            and (fmMain.sgTheme.Cells[0, i]<leThemeNumber.Text))
            OR
            ((cbThemeNumber.Text='=')
            and (fmMain.sgTheme.Cells[0, i]=leThemeNumber.Text))
            OR
            ((cbThemeNumber.Text='>')
            and (fmMain.sgTheme.Cells[0, i]>leThemeNumber.Text))
              then
          fmMain.sgTheme.RowHeights[i]:=-ABS(fmMain.sgTheme.RowHeights[i]);
      end;

      { Шукаємо II параметр. }
      if (leThemeName.Text<>'') then
      begin
        { Якщо є такий II_параметр і I_параметр не шукали - ставимо мітку }
        if (pos(leThemeName.Text, fmMain.sgTheme.Cells[1, i])>0)
            and (leThemeNumber.Text='') then
          fmMain.sgTheme.RowHeights[i]:=-ABS(fmMain.sgTheme.RowHeights[i]);
        { Якщо немає такого II_параметру, а попередній пошук знайшов - знімаємо мітку }
        if (pos(leThemeName.Text, fmMain.sgTheme.Cells[1, i])=0)
            and (fmMain.sgTheme.RowHeights[i]<0) then
          fmMain.sgTheme.RowHeights[i]:=ABS(fmMain.sgTheme.RowHeights[i]);
      end;

      { Шукаємо III параметр. }
      if (leThemePrice.Text<>'') and (leThemePrice.Enabled=True) then
      begin
        { Вибираємо метод оцінки < = > }
        { Якщо є такий III_параметр, і I-II_параметри не шукали - ставимо мітку }
        if ((cbThemePrice.Text='<')
            and (fmMain.sgTheme.Cells[2, i]<leThemePrice.Text)
            and (leThemeNumber.Text='')
            and (leThemeName.Text=''))
            OR
            ((cbThemePrice.Text='=')
            and (fmMain.sgTheme.Cells[2, i]=leThemePrice.Text)
            and (leThemeNumber.Text='')
            and (leThemeName.Text=''))
            OR
            ((cbThemePrice.Text='>')
            and (fmMain.sgTheme.Cells[2, i]>leThemePrice.Text)
            and (leThemeNumber.Text='')
            and (leThemeName.Text=''))
              then
          fmMain.sgTheme.RowHeights[i]:=-ABS(fmMain.sgTheme.RowHeights[i]);
        { Якщо немає такого III_параметру, а попередні пошуки знайшли - знімаємо мітку }
        if ((cbThemePrice.Text='<')
            and (fmMain.sgTheme.Cells[2,i]>=leThemePrice.Text)
            and (fmMain.sgTheme.RowHeights[i]<0))
            OR
            ((cbThemePrice.Text='=')
            and (fmMain.sgTheme.Cells[2,i]<>leThemePrice.Text)
            and (fmMain.sgTheme.RowHeights[i]<0))
            OR
            ((cbThemePrice.Text='>')
            and (fmMain.sgTheme.Cells[2,i]<=leThemePrice.Text)
            and (fmMain.sgTheme.RowHeights[i]<0))
              then
          fmMain.sgTheme.RowHeights[i]:=ABS(fmMain.sgTheme.RowHeights[i]);
      end;
    end;
  end;

  fmMain.sbSaveTable.Click;
  fmMain.sbOpenDB.Click;
end;


Эта процедура занимает 250 строк (включая комментарии).
Это нормальная практика для программиста вообще и для Delphi-программиста в частности? Так и должно быть?

З.Ы.: Не было времени переводить комментарии на русский, потому извиняйте.
Также некоторые условия не оптимизированы. По-этому не швыряться тапками =) (Знаю Pascal 1 год, Delphi - 1/2 года).

Автор: Ulysses4j 9.6.2007, 01:51
Код я не читал, но 250 мне кажется очень много. Часто можно услышать фразу Дейкстры о том, что код метода должен помещаться на экране. Это очень оптимистично, конечно, но стоит к этому стремиться, мне кажется. Некоторые исследования на эту тему приведены в замечательной книжке Стива МакКоннелла "Совершенный код" (п. 7.4), которую можно достать в сети. У него всего сходится к тому, что лучше бы ограничивать длину метода 200-ми строками.

Автор: 4d5a 14.6.2007, 11:29
У меня обычно методы растягиваются по двум причинам 1) Метод задумывался чтобы решить одну задачу, потом универсализовывался для решения нескольких схожих
2) Когда решение сваливается в разбор разных частных случаев (иногда это необходимо для оптимизации) 
В основном получается меньше 250 строк
Многие считают что задачу надо дифференцировать до предела что бы легче решалась. Мне лично иногда хочется что бы все решение было целостное и заключалось в одном методе.
(сейчас работаю над: http://csys.webhost.ru ) 
Вот САМОЕ длинное что смог найти в написанном за последние 2 месяца 


процедура (delphi) для dll'ки кодирования данных, Этот метод растягивает/сжимает буфер побайтно, advanced мажорирование типа.

Код

{
coment to zoom byte
      FZ_EMPT  = 5601;  // Easy metod,use first char in zn_ sub array
      FZ_NFRQ  = 5602;  // if (zn<0) Take into frq param
      FZ_EDNZ  = 5603;  // _EMP use  n_z ;;; if (zn<0 & if n1- Int n1/zn > 0.5 ) size +1

ie 2 charackteristics consider
 aligment and fill method
}
function ZoomBufByte(inD:TBuf;var outD:TBuf;zn :integer;
        FLAG:integer=FZ_EMPT;n_z:integer=0):integer;
var m,c,k,i,n1,n2,j,inc_inx:integer;
    tmp,tmp1:TBuf;
    s:longint;
    t3:byte;
begin
 result:=-1;
 n1:=Length(inD);
 inc_inx:=0;
 if ((FLAG=FZ_EDNZ)and(n_z<abs(zn))) then inc_inx:=n_z;
 if ( (n1<1)or(zn=0) ) then exit ;
 if (zn>0) then
  begin
   n2:=n1*zn;
   SetLength(outD,n2);
   for i:=0 to n1-1 do
    for j:=0 to zn-1 do
     outD[i*zn+j]:=inD[i];
  end else
  begin
   zn:=abs(zn);
   if (zn>n1) then
    begin
     n2:=1;
     SetLength(outD,1);
     outD[0]:=InD[0];
     exit;
    end;
   n2:=n1 div zn;
   if (FLAG=FZ_EDNZ) then
     if ((n1 mod zn)>(zn div 2)) then Inc(n2);
   SetLength(outD,n2);
   i:=0;j:=0;
   if (FLAG=FZ_NFRQ) then
    begin
       SetLength(tmp,zn); SetLength(tmp1,zn);
       while (j<n2) do
       begin
         c:=0;
         for k:=0 to zn-1 do tmp1[k]:=0;
         for k:=0 to zn-1 do
          begin
           s:=InD[i+k];
           for m:=0 to c-1 do
             if (tmp[m]=s) then
              begin Inc(tmp1[m]); s:=-1; end;
           if (s>=0) then
             begin tmp[c]:=s; Inc(tmp1[c]); Inc(c); end;
          end;
         s:=0;
         for k:=0 to c-1 do
          if (tmp1[k]>s) then
                begin
                 s:=tmp1[k];
                 m:=tmp[k];
                end;
         OutD[j]:=m;
         i:=i+zn;
         Inc(j);
       end;
    end  else
   if (FLAG=FZ_NRSA) then
    begin
      if (n_z<0) then
      begin
       while (j<n2) do
       begin
        s:=0; i:=j*zn;
        for k:=0 to zn-1 do
         s:=s+inD[i+k];
        t3:=round(s/zn);
        m:=0;
        for k:=0 to zn-1 do
         if ( abs(t3-InD[i+k])<abs(t3-InD[i+m]) ) then  m:=k;
        OutD[j]:=InD[i+m];
        Inc(j);
       end;
      end else
      while (j<n2) do
       begin
        s:=0; i:=j*zn;
        for k:=0 to zn-1 do
         s:=s+inD[i+k];
        outD[j]:=round(s/zn);
        Inc(j);
       end;
    end else
   if (FLAG=FZ_NRSE) then
    begin
     SetLength(tmp,zn);
     if (n_z=0) then
      while (j<n2) do
       begin
        for k:=0 to zn-1 do
         tmp[k]:=InD[i+k];
        for k:=0 to zn-1 do
         begin
         s:=1;
         for m:=1 to zn-k-1 do
          if (tmp[m-1]>tmp[m]) then
           begin t3:=tmp[m-1]; tmp[m-1]:=tmp[m]; tmp[m]:=t3; s:=0;end;
         if (s>0) then break;
         end;
        outD[j]:=tmp[zn div 2];
        inc(i,zn);
        inc(j);
       end;
    end else
   if (FLAG=FZ_EMPT) then
    begin
     while (j<n2) do
      begin
        outD[j]:=inD[i];
        i:=i+zn+inc_inx;
        Inc(j);
      end
    end else
   if (FLAG=FZ_EDNZ) then
    begin
     i:=inc_inx;
     while (i<n1) do
      begin
        outD[j]:=inD[i];
        i:=i+zn;
        Inc(j);
      end;
     if (j=(n2-1)) then
       outD[j]:=inD[n1-1];
    end;
  end;
 result:=n2;
end;


Добавлено через 6 минут и 38 секунд
Считаю что 250 конечно многовато но если надо ...
насчет "нормальная практика" открой исходники делфы и посмотри - В основном методы менее 100 строк но есть и приличненькие. А вообщето главное в методе чтобы он 1) Выполнял свою задачу и ТОЛЬКО! 2) Делала это как можно более оптимально 3) Код  был симпатичным хотябы (не буду говорить о красоте)

Добавлено через 9 минут и 43 секунды
у можно в двух словах задачу TfmDeleteRecords.sbDeleteRecordsClick ?

Добавлено через 10 минут и 47 секунд
replace:
LastMsg[1]:='А'

smile

Автор: Alexeis 14.6.2007, 12:23
Цитата(4d5a @  14.6.2007,  11:29 Найти цитируемый пост)
Метод задумывался чтобы решить одну задачу, потом универсализовывался для решения нескольких схожих

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

Автор: TXC 17.6.2007, 00:19
4d5a, 

Цитата(4d5a @  14.6.2007,  11:29 Найти цитируемый пост)
у можно в двух словах задачу TfmDeleteRecords.sbDeleteRecordsClick ?

Удаляет запись по определенным характеристикам определенных полей.

Автор: 4d5a 18.6.2007, 05:04
Alexeis, в некоторых случаях не так все просто smile как может показаться

Автор: Earnest 18.6.2007, 13:01
Цитата(4d5a @  18.6.2007,  06:04 Найти цитируемый пост)
Alexeis, в некоторых случаях не так все просто  как может показаться 

Практика показывает, что путем некоторого напряжения мозгов метод всегда можно сократить до приемлимой величины. МакКоннел, на которого уже ссылались, предлагает свою метрику для оценки приемлимости длины метода. И это не число строк кода, а число неких точек принятия решения. Которых в идеале не должно быть больше 10 или, в крайнем случае, 20.
Длинные методы может и легко писать, но сложно поддерживать: это главный аргумент против них.
Если функция, как ей и положено, выполняет ровно одну задачу, ее несложно разбить и сократить. А если она выполняет не одну задачу - нужно переделывать дизайн.

Автор: mes 19.6.2007, 09:44
Так у тебя  уже выделено в процедуре три логических метода, осталось только физически разместить содержимое: 

  
Код

{------------------------------------}
  { Якщо видаляємо записи користувачів }
  {------------------------------------}
  if fmDeleteRecords.cbChangeDB.Text='База користувачів' then ... {1 submethod}
  {---------------------------}
  { Якщо видаляємо записи тем }
  {---------------------------}
  if fmDeleteRecords.cbChangeDB.Text='База тем' then .. {2 submethod}
  {------------------------------}
  { Якщо видаляємо записи питань }
  {------------------------------}
  if fmDeleteRecords.cbChangeDB.Text='База питань' then  ...{3 submethod}
  
  ...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)