programing tip

좋아하는 (영리한) 방어 프로그래밍 모범 사례

itbloger 2020. 6. 12. 21:43
반응형

좋아하는 (영리한) 방어 프로그래밍 모범 사례


방어 코딩을 위해

좋아하는

(영리한) 기술 을 선택해야한다면 그 기술은 무엇입니까? 현재 사용하는 언어는 Java 및 Objective-C (C ++의 배경 지식)이지만 모든 언어로 자유롭게 대답 할 수 있습니다. 여기서 우리 중 70 % 이상이 이미 알고있는 것 이외의

영리한

방어 기술 에 중점 을 둘 것입니다. 이제 트릭을 깊이 파고들 차례입니다.다시

말해이 흥미없는

이외의 다른 것을 생각해보십시오 .

  • if(5 == x) 대신 if(x == 5) : 의도하지 않은 할당을 피하기 위해

다음은

흥미로운

방어 적 프로그래밍 방법에 대한 몇 가지 예입니다 (언어 별 예는 Java로되어 있음).

-변수를 변경해야 할 때까지 변수를 잠그십시오.

즉, 변수 를 변경해야한다는 것을 알 때까지

모든

변수를 선언 할 수 있으며이

final

시점에서을 제거 할 수 있습니다

final

. 일반적으로 알려지지 않은 사실은 이것이 메소드 매개 변수에도 유효하다는 것입니다.

public void foo(final int arg) { /* Stuff Here */ }

-나쁜 일이 생길 때는 증거를 남기십시오

예외가있을 때 수행 할 수있는 여러 가지 작업이 있습니다. 명백하게 기록하고 정리를 수행하는 것은 몇 가지 일 것입니다. 그러나 증거의 흔적을 남길 수도 있습니다 (예 : "UNABLE TO LOAD FILE"또는 99999와 같은 센티넬 값으로 변수를 설정하면 예외

catch

블록을 넘어서 버릴 경우 디버거에서 유용 할 수 있습니다 ).

-일관성에 관해서 : 악마는 세부 사항에 있습니다.

사용중인 다른 라이브러리와 일관성을 유지하십시오. 예를 들어 Java에서 값 범위를 추출하는 메소드를 작성하는 경우 하한을

포함

하고 상한을

독점으로 설정하십시오

. 이것은

String.substring(start, end)

같은 방식으로 작동하는 것과 같은 방법과 일치하게합니다 . Sun JDK에서 이러한 유형의 모든 메소드는 배열이 일치하는 요소의 반복을 포함하여 다양한 작업을 수행하므로 인덱스가 0 (

포함

)부터 배열의 길이 (

독점

) 까지 다양한 방식으로 작동하므로 이러한 방식으로 작동합니다 .그렇다면 가장 좋아하는 수비 관행은 무엇입니까?

업데이트 : 아직하지 않은 경우 자유롭게 차임하십시오. 공식 답변을 선택하기 전에 더 많은 답변을 드리겠습니다.


c ++에서는 펜스 포스트 오류를 ​​포착하기 위해 여분의 메모리를 제공하기 위해 새로운 재정의를 한 번 좋아했습니다. 현재 저는

테스트 주도 개발 (Test Driven Development)을

선호하는 방어 적 프로그래밍을 피하는 것을 선호합니다 . 오류를 신속하고 외부 적으로 파악하는 경우 방어적인 조작으로 코드를 어지럽 힐 필요가 없으며 코드가

건조

되어 방어해야 할 오류가 줄어 듭니다.

WikiKnowledge가 쓴대로

:

방어 프로그래밍을 피하고 대신 빨리 실패하십시오.

방어 적 프로그래밍이란 데이터의 일부 실패를 보상하려고 시도하는 코드 작성, 호출자가 호출자와 서브 루틴 간의 계약을 준수하지 않는 데이터를 제공 할 수 있고 서브 루틴이 어떻게 든 대처해야한다고 가정하는 코드 작성 습관을 의미합니다. 그것으로.

SQL

데이터를 삭제해야 할 때

select *    
--delete    
From mytable    
Where ...

내가 그것을 실행할 때, where 절을 잊어 버렸는지 알 수 있습니다. 안전이 있습니다. 모든 것이 괜찮다면 '-'주석 토큰 다음에있는 모든 것을 강조 표시하고 실행합니다.편집 : 많은 데이터를 삭제하는 경우 * 대신 count (*)를 사용합니다.


응용 프로그램이 시작될 때 합리적인 메모리 덩어리를 할당하십시오. Steve McConnell은 이것을 Code Complete

메모리 낙하산

이라고 부릅니다 .심각한 문제가 발생하여 종료해야하는 경우에 사용할 수 있습니다. 이 메모리를 미리 할당하면 여유 공간을 확보 한 후 사용 가능한 메모리를 사용하여 다음을 수행 할 수 있으므로 안전망이 제공됩니다.

  • 모든 영구 데이터 저장
  • 적절한 파일을 모두 닫습니다
  • 로그 파일에 오류 메시지 쓰기
  • 사용자에게 의미있는 오류를 제시

기본 사례가없는 모든 switch 문에서 오류 메시지와 함께 프로그램을 중단하는 사례를 추가합니다.

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}

열거 형 (C #)의 다양한 상태를 처리 할 때 :

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

그런 다음, 일상 안에서 ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

언젠가이 열거 형에 다른 계정 유형을 추가하겠습니다. 그리고 내가 할 때, 나는이 스위치 문장을 고치는 것을 잊을 것이다. 따라서이

Debug.Fail

사실에주의를 끌기 위해 디버그 모드에서 끔찍하게 충돌이 발생합니다. 를 추가하면

case AccountType.MyNewAccountType:

다른 계정 유형을 추가하고 여기에서 사례를 업데이트하는 것을 잊을 때까지 끔찍한 충돌이 중지됩니다.(예, 다형성이 아마도 더 좋을 수도 있지만 이것은 단지 내 머리 꼭대기의 예일뿐입니다.)


문자열 (특히 사용자 입력에 따라 다름)과 함께 오류 메시지를 인쇄 할 때 항상 작은 따옴표를 사용합니다

''

. 예를 들면 다음과 같습니다.

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

 

%s

파일 이름이 빈 문자열이거나 공백 또는 무언가이기 때문에 따옴표 부족합니다 . 인쇄 된 메시지는 물론 다음과 같습니다.

ERROR: Could not open file

따라서 항상하는 것이 좋습니다.

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

그런 다음 최소한 사용자에게 다음이 표시됩니다.

ERROR: Could not open file ''

최종 사용자가 제출 한 버그 보고서의 품질 측면에서 큰 차이가 있습니다. 일반적인 소리가 아닌 이와 같은 재미있는 오류 메시지가있는 경우 "내 파일을 열지 않습니다"라고 쓰지 않고 복사 / 붙여 넣기 할 가능성이 훨씬 높습니다.


SQL 안전

데이터를 수정할 SQL을 작성하기 전에 롤백 트랜잭션으로 전체를 래핑합니다.

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

이렇게하면 잘못된 삭제 / 업데이트를 영구적으로 실행할 수 없습니다. 또한 모든 것을 실행하고 합리적인 레코드 수를 확인하거나

SELECT

SQL과 사이 문을 추가 하여

ROLLBACK TRANSACTION

모든 것이 올바르게 표시되도록 할 수 있습니다.완전히있을 때 확실히 당신이 기대했던의 변경 않습니다

ROLLBACK

COMMIT

와 진짜 실행합니다.


모든 언어의 경우 :

변수의 범위를

가능한 최소로 줄이십시오 . 다음 명령문으로 전달하기 위해 제공된

변수

를 피하십시오. 존재하지 않는 변수는 이해할 필요가없는 변수이며 책임을 질 수 없습니다. 같은 이유로 가능할 때마다 Lambdas를 사용하십시오.


Java에서, 특히 콜렉션이있는 경우 API를 사용하십시오. 따라서 메소드가 List 유형 (예 : 유형)을 리턴하는 경우 다음을 시도하십시오.

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

수업에서 피할 필요가없는 물건은 피하십시오!


의심스러운 경우 응용 프로그램을 폭파하십시오!

각각의

 

모든

방법 의 시작 부분에서 각각모든 매개 변수를 확인 하고 (명확하게 코딩하거나 계약 기반 프로그래밍을 사용하는 것은 중요하지 않음) 올바른 사전 예외 및 / 또는 코드의 전제 조건이 충족시키지 못함.우리

는 코드를 작성할 때

이러한 암시 적 전제 조건에 대해 알고 있지만, 명시 적으로 검사하지 않으면 나중에 문제가 발생했을 때 스스로 미로를 만들고 있으며 수십 번의 메소드 호출이 증상의 발생과 실제 위치를 분리합니다. 전제 조건이 충족되지 않은 경우 (= 문제 / 버그가 실제로있는 위치)


펄에서는

use warnings;

나는 좋아한다

use warnings FATAL => 'all';

이로 인해 컴파일러 / 런타임 경고로 인해 코드가 종료됩니다. 이것은 초기화되지 않은 문자열을 잡는 데 주로 유용합니다.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here

씨#:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}

C #에서 string.IsNullOrEmpty를 확인하기 전에 length, indexOf, mid 등과 같은 문자열에 대한 작업을 수행하기 전에

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[편집]

이제 .NET 4.0에서 다음을 수행 할 수 있습니다. 값이 공백인지 추가로 확인합니다.

string.IsNullOrWhiteSpace(myString)

In Java and C#, give every thread a meaningful name. This includes thread pool threads. It makes stack dumps much more meaningful. It takes a little more effort to give a meaningful name to even thread pool threads, but if one thread pool has a problem in a long running application, I can cause a stack dump to occur (you do know about SendSignal.exe, right?), grab the logs, and without having to interrupt a running system I can tell which threads are ... whatever. Deadlocked, leaking, growing, whatever the problem is.


With VB.NET, have Option Explicit and Option Strict switched on by default for the whole of Visual Studio.


With Java, it can be handy to make use of the assert keyword, even if you run production code with assertions turned off:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

Even with assertions off, at least the code documents the fact that param is expected to be set somewhere. Note that this is a private helper function and not a member of a public API. This method can only be called by you, so it's OK to make certain assumptions on how it will be used. For public methods, it's probably better to throw a real exception for invalid input.


I didn't find the readonly keyword until I found ReSharper, but I now use it instinctively, especially for service classes.

readonly var prodSVC = new ProductService();

In Java, when something is happening and I don't know why, I will sometimes use Log4J like this:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

this way I get a stack trace showing me how I got into the unexpected situation, say a lock that never unlocked, something null that cannot be null, and so on. Obviously, if a real Exception is thrown, I don't need to do this. This is when I need to see what is happening in production code without actually disturbing anything else. I don't want to throw an Exception and I didn't catch one. I just want a stack trace logged with an appropriate message to flag me in to what is happening.


C++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

then replace all your 'delete pPtr' and 'delete [] pPtr' calls with SAFE_DELETE(pPtr) and SAFE_DELETE_ARRAY(pPtr)

Now by mistake if you use the pointer 'pPtr' after deleting it, you will get 'access violation' error. It is far easier to fix than random memory corruptions.


C#

  • Verify non-null values for reference type parameters in public method.
  • I use sealed a lot for classes to avoid introducing dependencies where I didn't want them. Allowing inheritance should be done explicitly and not by accident.

If you are using Visual C++, utilize the override keyword whenever you over-ride a base class's method. This way if anyone ever happens to change the base class signature, it will throw a compiler error rather than the wrong method being silently called. This would have saved me a few times if it had existed earlier.

Example:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}

I've learned in Java to almost never wait indefinitely for a lock to unlock, unless I truly expect that it may take an indefinitely long time. If realistically, the lock should unlock within seconds, then I'll wait only for a certain length of time. If the lock does not unlock, then I complain and dump stack to the logs, and depending on what is best for the stability of the system, either continue on as if the lock unlocked, or continue as if the lock never unlocked.

This has helped isolate a few race conditions and pseudo-deadlock conditions that were mysterious before I started doing this.


When you issue an error message, at least attempt to provide the same information the program had when it made the decision to throw an error.

"Permission denied" tells you there was a permission problem, but you have no idea why or where the problem occurred. "Can't write transaction log /my/file: Read-only filesystem" at least lets you know the basis on which the decision was made, even if it's wrong - especially if it's wrong: wrong file name? opened wrong? other unexpected error? - and lets you know where you were when you had the problem.


In C#, use the as keyword to cast.

string a = (string)obj

will throw an exception if obj is not a string

string a = obj as string

will leave a as null if obj is not a string

You still need to take null into account, but that is typically more straight forward then looking for cast exceptions. Sometimes you want "cast or blow up" type behavior, in which case (string)obj syntax is preferred.

In my own code, I find I use the as syntax about 75% of the time, and (cast) syntax about 25%.


Java

The java api has no concept of immutable objects, which is bad! Final can help you in that case. Tag every class that is immutable with final and prepare the class accordingly.

Sometimes it is useful to use final on local variables to make sure they never change their value. I found this useful in ugly, but necessary loop constructs. Its just to easy to accidently reuse a variable even though it is mend to be a constant.

Use defense copying in your getters. Unless you return a primitive type or a immutable object make sure you copy the object to not violate encapsulation.

Never use clone, use a copy constructor.

Learn the contract between equals and hashCode. This is violated so often. The problem is it doesn't affect your code in 99% of the cases. People overwrite equals, but don't care about hashCode. There are instances in wich your code can break or behaves strange, e.g. use mutable objects as keys in a map.


Be prepared for any input, and any input you get that is unexpected, dump to logs. (Within reason. If you're reading passwords from the user, don't dump that to logs! And don't log thousands of these sorts of messages to logs per second. Reason about the content and likelihood and frequency before you log it.)

I'm not just talking about user input validation. For example, if you are reading HTTP requests that you expect to contain XML, be prepared for other data formats. I was surprised to see HTML responses where I expected only XML -- until I looked and saw that my request was going through a transparent proxy I was unaware of and that the customer claimed ignorance of -- and the proxy timed out trying to complete the request. Thus the proxy returned an HTML error page to my client, confusing the heck out of the client that expected only XML data.

Thus, even when you think you control both ends of the wire, you can get unexpected data formats without any villainy being involved. Be prepared, code defensively, and provide diagnostic output in the case of unexpected input.


I try to use Design by Contract approach. It can be emulated run time by any language. Every language supports "assert", but it's easy and covenient to write a better implementation that let you manage the error in a more useful way.

In the Top 25 Most Dangerous Programming Errors the "Improper Input Validation" is the most dangerous mistake in the "Insecure Interaction Between Components" section.

Adding precondition asserts at the beginning of the methods is a good way to be sure that parameters are consistent. At the end of methods i write postconditions, that check that output is what's inteded to be.

In order to implement invariants, I write a method in any class that checks "class consistence", that should be called authomatically by precondition and postcondition macro.

I'm evaluating the Code Contract Library.


I forgot to write echo in PHP one too many times:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

It would take me forever to try and figure out why ->baz() wasn't returning anything when in fact I just wasn't echoing it! :-S So I made an EchoMe class which could be wrapped around any value that should be echoed:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

And then for the development environment, I used an EchoMe to wrap things which should be printed:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

Using that technique, the first example missing the echo would now throw an exception ...


C++

When I type new, I must immediately type delete. Especially for arrays.

C#

Check for null before accessing properties, especially when using the Mediator pattern. Objects get passed (and then should be cast using as, as has already been noted), and then check against null. Even if you think it will not be null, check anyway. I've been surprised.


Use a logging system that allows dynamic, run time log level adjustments. Often if you have to stop a program to enable logging, you'll lose whatever rare state the bug occurred in. You need to be able to turn on more logging information without stopping the process.

Also, 'strace -p [pid]' on linux will show you want system calls a process (or linux thread) is making. It may look strange at first, but once you get used to what system calls are generally made by what libc calls, you'll find this invaluable for in the field diagnosis.

참고URL : https://stackoverflow.com/questions/490420/favorite-clever-defensive-programming-best-practices

반응형